@@ -53,6 +53,20 @@ func (ps *programSize) RAM() uint64 {
53
53
return ps .Data + ps .BSS
54
54
}
55
55
56
+ // Return the package size information for a given package path, creating it if
57
+ // it doesn't exist yet.
58
+ func (ps * programSize ) getPackage (path string ) * packageSize {
59
+ if field , ok := ps .Packages [path ]; ok {
60
+ return field
61
+ }
62
+ field := & packageSize {
63
+ Program : ps ,
64
+ Sub : map [string ]* packageSize {},
65
+ }
66
+ ps .Packages [path ] = field
67
+ return field
68
+ }
69
+
56
70
// packageSize contains the size of a package, calculated from the linked object
57
71
// file.
58
72
type packageSize struct {
@@ -61,6 +75,7 @@ type packageSize struct {
61
75
ROData uint64
62
76
Data uint64
63
77
BSS uint64
78
+ Sub map [string ]* packageSize
64
79
}
65
80
66
81
// Flash usage in regular microcontrollers.
@@ -79,6 +94,25 @@ func (ps *packageSize) FlashPercent() float64 {
79
94
return float64 (ps .Flash ()) / float64 (ps .Program .Flash ()) * 100
80
95
}
81
96
97
+ // Add a single size data point to this package.
98
+ // This must only be called while calculating package size, not afterwards.
99
+ func (ps * packageSize ) addSize (getField func (* packageSize , bool ) * uint64 , filename string , size uint64 , isVariable bool ) {
100
+ if size == 0 {
101
+ return
102
+ }
103
+
104
+ // Add size for the package.
105
+ * getField (ps , isVariable ) += size
106
+
107
+ // Add size for file inside package.
108
+ sub , ok := ps .Sub [filename ]
109
+ if ! ok {
110
+ sub = & packageSize {Program : ps .Program }
111
+ ps .Sub [filename ] = sub
112
+ }
113
+ * getField (sub , isVariable ) += size
114
+ }
115
+
82
116
// A mapping of a single chunk of code or data to a file path.
83
117
type addressLine struct {
84
118
Address uint64
@@ -796,40 +830,32 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
796
830
program := & programSize {
797
831
Packages : sizes ,
798
832
}
799
- getSize := func (path string ) * packageSize {
800
- if field , ok := sizes [path ]; ok {
801
- return field
802
- }
803
- field := & packageSize {Program : program }
804
- sizes [path ] = field
805
- return field
806
- }
807
833
for _ , section := range sections {
808
834
switch section .Type {
809
835
case memoryCode :
810
- readSection (section , addresses , func (path string , size uint64 , isVariable bool ) {
811
- field := getSize (path )
836
+ readSection (section , addresses , program , func (ps * packageSize , isVariable bool ) * uint64 {
812
837
if isVariable {
813
- field .ROData += size
814
- } else {
815
- field .Code += size
838
+ return & ps .ROData
816
839
}
840
+ return & ps .Code
817
841
}, packagePathMap )
818
842
case memoryROData :
819
- readSection (section , addresses , func (path string , size uint64 , isVariable bool ) {
820
- getSize ( path ) .ROData += size
843
+ readSection (section , addresses , program , func (ps * packageSize , isVariable bool ) * uint64 {
844
+ return & ps .ROData
821
845
}, packagePathMap )
822
846
case memoryData :
823
- readSection (section , addresses , func (path string , size uint64 , isVariable bool ) {
824
- getSize ( path ) .Data += size
847
+ readSection (section , addresses , program , func (ps * packageSize , isVariable bool ) * uint64 {
848
+ return & ps .Data
825
849
}, packagePathMap )
826
850
case memoryBSS :
827
- readSection (section , addresses , func (path string , size uint64 , isVariable bool ) {
828
- getSize ( path ) .BSS += size
851
+ readSection (section , addresses , program , func (ps * packageSize , isVariable bool ) * uint64 {
852
+ return & ps .BSS
829
853
}, packagePathMap )
830
854
case memoryStack :
831
855
// We store the C stack as a pseudo-package.
832
- getSize ("C stack" ).BSS += section .Size
856
+ program .getPackage ("C stack" ).addSize (func (ps * packageSize , isVariable bool ) * uint64 {
857
+ return & ps .BSS
858
+ }, "" , section .Size , false )
833
859
}
834
860
}
835
861
@@ -844,8 +870,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
844
870
}
845
871
846
872
// readSection determines for each byte in this section to which package it
847
- // belongs. It reports this usage through the addSize callback.
848
- func readSection (section memorySection , addresses []addressLine , addSize func (string , uint64 , bool ), packagePathMap map [string ]string ) {
873
+ // belongs.
874
+ func readSection (section memorySection , addresses []addressLine , program * programSize , getField func (* packageSize , bool ) * uint64 , packagePathMap map [string ]string ) {
849
875
// The addr variable tracks at which address we are while going through this
850
876
// section. We start at the beginning.
851
877
addr := section .Address
@@ -867,9 +893,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
867
893
addrAligned := (addr + line .Align - 1 ) &^ (line .Align - 1 )
868
894
if line .Align > 1 && addrAligned >= line .Address {
869
895
// It is, assume that's what causes the gap.
870
- addSize ("(padding)" , line .Address - addr , true )
896
+ program . getPackage ("(padding)" ). addSize ( getField , " " , line .Address - addr , true )
871
897
} else {
872
- addSize ("(unknown)" , line .Address - addr , false )
898
+ program . getPackage ("(unknown)" ). addSize ( getField , " " , line .Address - addr , false )
873
899
if sizesDebug {
874
900
fmt .Printf ("%08x..%08x %5d: unknown (gap), alignment=%d\n " , addr , line .Address , line .Address - addr , line .Align )
875
901
}
@@ -891,7 +917,8 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
891
917
length = line .Length - (addr - line .Address )
892
918
}
893
919
// Finally, mark this chunk of memory as used by the given package.
894
- addSize (findPackagePath (line .File , packagePathMap ), length , line .IsVariable )
920
+ packagePath , filename := findPackagePath (line .File , packagePathMap )
921
+ program .getPackage (packagePath ).addSize (getField , filename , length , line .IsVariable )
895
922
addr = line .Address + line .Length
896
923
}
897
924
if addr < sectionEnd {
@@ -900,9 +927,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
900
927
if section .Align > 1 && addrAligned >= sectionEnd {
901
928
// The gap is caused by the section alignment.
902
929
// For example, if a .rodata section ends with a non-aligned string.
903
- addSize ("(padding)" , sectionEnd - addr , true )
930
+ program . getPackage ("(padding)" ). addSize ( getField , " " , sectionEnd - addr , true )
904
931
} else {
905
- addSize ("(unknown)" , sectionEnd - addr , false )
932
+ program . getPackage ("(unknown)" ). addSize ( getField , " " , sectionEnd - addr , false )
906
933
if sizesDebug {
907
934
fmt .Printf ("%08x..%08x %5d: unknown (end), alignment=%d\n " , addr , sectionEnd , sectionEnd - addr , section .Align )
908
935
}
@@ -912,17 +939,25 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
912
939
913
940
// findPackagePath returns the Go package (or a pseudo package) for the given
914
941
// path. It uses some heuristics, for example for some C libraries.
915
- func findPackagePath (path string , packagePathMap map [string ]string ) string {
942
+ func findPackagePath (path string , packagePathMap map [string ]string ) ( packagePath , filename string ) {
916
943
// Check whether this path is part of one of the compiled packages.
917
944
packagePath , ok := packagePathMap [filepath .Dir (path )]
918
- if ! ok {
945
+ if ok {
946
+ // Directory is known as a Go package.
947
+ // Add the file itself as well.
948
+ filename = filepath .Base (path )
949
+ } else {
919
950
if strings .HasPrefix (path , filepath .Join (goenv .Get ("TINYGOROOT" ), "lib" )) {
920
951
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
921
- // package, with a "C" prefix. For example: "C compiler-rt" for the
922
- // compiler runtime library from LLVM.
923
- packagePath = "C " + strings .Split (strings .TrimPrefix (path , filepath .Join (goenv .Get ("TINYGOROOT" ), "lib" )), string (os .PathSeparator ))[1 ]
924
- } else if strings .HasPrefix (path , filepath .Join (goenv .Get ("TINYGOROOT" ), "llvm-project" )) {
952
+ // package, with a "C" prefix. For example: "C picolibc" for the
953
+ // baremetal libc.
954
+ libPath := strings .TrimPrefix (path , filepath .Join (goenv .Get ("TINYGOROOT" ), "lib" )+ string (os .PathSeparator ))
955
+ parts := strings .SplitN (libPath , string (os .PathSeparator ), 2 )
956
+ packagePath = "C " + parts [0 ]
957
+ filename = parts [1 ]
958
+ } else if prefix := filepath .Join (goenv .Get ("TINYGOROOT" ), "llvm-project" , "compiler-rt" ); strings .HasPrefix (path , prefix ) {
925
959
packagePath = "C compiler-rt"
960
+ filename = strings .TrimPrefix (path , prefix + string (os .PathSeparator ))
926
961
} else if packageSymbolRegexp .MatchString (path ) {
927
962
// Parse symbol names like main$alloc or runtime$string.
928
963
packagePath = path [:strings .LastIndex (path , "$" )]
@@ -945,9 +980,11 @@ func findPackagePath(path string, packagePathMap map[string]string) string {
945
980
// fixed in the compiler.
946
981
packagePath = "-"
947
982
} else {
948
- // This is some other path. Not sure what it is, so just emit its directory.
949
- packagePath = filepath .Dir (path ) // fallback
983
+ // This is some other path. Not sure what it is, so just emit its
984
+ // directory as a fallback.
985
+ packagePath = filepath .Dir (path )
986
+ filename = filepath .Base (path )
950
987
}
951
988
}
952
- return packagePath
989
+ return
953
990
}
0 commit comments