From 792fc04ba369c24035c4a35c237568ab2f84427f Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Tue, 26 Apr 2022 00:43:29 +0300 Subject: [PATCH 1/5] Table output for segment size script Also include maximum aka total for every segment key Re-format the file and clean-up the resulting data dict --- tools/sizes.py | 141 +++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 56 deletions(-) diff --git a/tools/sizes.py b/tools/sizes.py index 7ce4ff1d99..d5e737279f 100755 --- a/tools/sizes.py +++ b/tools/sizes.py @@ -17,74 +17,103 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from __future__ import print_function import argparse import os import subprocess import sys +import contextlib + + +def get_segment_hints(): + return { + "ICACHE": "flash instruction cache", + "IROM": "code in flash (default, ICACHE_FLASH_ATTR)", + "IRAM": "code in IRAM (IRAM_ATTR, ICACHE_RAM_ATTR)", + "DATA": "initialized variables (global, static) in RAM", + "RODATA": "constants (global, static) in RAM", + "BSS": "zeroed variables (global, static) in RAM", + } + + +def get_segment_sizes(elf, path, mmu): + sizes = { + "ICACHE": [32768, 32768], + "IROM": [0, 1048576], + "IRAM": [0, 32768], + "DATA": [0, 81920], + "RODATA": [0, 81920], + "BSS": [0, 81920], + } + + cmd = [os.path.join(path, "xtensa-lx106-elf-size"), "-A", elf] + with subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) as proc: + lines = proc.stdout.readlines() + for line in lines: + words = line.split() + if line.startswith(".irom0.text"): + sizes["IROM"][0] += int(words[1]) + elif line.startswith(".text"): + sizes["IRAM"][0] += int(words[1]) + elif line.startswith(".data"): + sizes["DATA"][0] += int(words[1]) + elif line.startswith(".rodata"): + sizes["RODATA"][0] += int(words[1]) + elif line.startswith(".bss"): + sizes["BSS"][0] += int(words[1]) + + for line in mmu.split(): + words = line.split("=") + if line.startswith("-DMMU_IRAM_SIZE"): + sizes["IRAM"][1] = int(words[1], 16) + elif line.startswith("-DMMU_ICACHE_SIZE"): + sizes["ICACHE"][0] = sizes["ICACHE"][1] = int(words[1], 16) -def get_segment_hints(iram): - hints = {} - hints['ICACHE'] = ' - flash instruction cache' - hints['IROM'] = ' - code in flash (default or ICACHE_FLASH_ATTR)' - hints['IRAM'] = ' / ' + str(iram) + ' - code in IRAM (IRAM_ATTR, ISRs...)' - hints['DATA'] = ') - initialized variables (global, static) in RAM/HEAP' - hints['RODATA'] = ') / 81920 - constants (global, static) in RAM/HEAP' - hints['BSS'] = ') - zeroed variables (global, static) in RAM/HEAP' - return hints - -def get_segment_sizes(elf, path): - sizes = {} - sizes['ICACHE'] = 0 - sizes['IROM'] = 0 - sizes['IRAM'] = 0 - sizes['DATA'] = 0 - sizes['RODATA'] = 0 - sizes['BSS'] = 0 - p = subprocess.Popen([path + "/xtensa-lx106-elf-size", '-A', elf], stdout=subprocess.PIPE, universal_newlines=True ) - lines = p.stdout.readlines() - for line in lines: - words = line.split() - if line.startswith('.irom0.text'): - sizes['IROM'] = sizes['IROM'] + int(words[1]) - elif line.startswith('.text'): # Gets .text and .text1 - sizes['IRAM'] = sizes['IRAM'] + int(words[1]) - elif line.startswith('.data'): # Gets .text and .text1 - sizes['DATA'] = sizes['DATA'] + int(words[1]) - elif line.startswith('.rodata'): # Gets .text and .text1 - sizes['RODATA'] = sizes['RODATA'] + int(words[1]) - elif line.startswith('.bss'): # Gets .text and .text1 - sizes['BSS'] = sizes['BSS'] + int(words[1]) return sizes -def get_mmu_sizes(mmu, sizes): - iram = 0x8000 - sizes['ICACHE'] = 0x8000 - lines = mmu.split(' ') - for line in lines: - words = line.split('=') - if line.startswith('-DMMU_IRAM_SIZE'): - iram = int(words[1], 16) - elif line.startswith('-DMMU_ICACHE_SIZE'): - sizes['ICACHE'] = int(words[1], 16) - return [iram, sizes] + +def percentage(lhs, rhs): + return "{}%".format(int(100.0 * float(lhs) / float(rhs))) + def main(): - parser = argparse.ArgumentParser(description='Report the different segment sizes of a compiled ELF file') - parser.add_argument('-e', '--elf', action='store', required=True, help='Path to the Arduino sketch ELF') - parser.add_argument('-p', '--path', action='store', required=True, help='Path to Xtensa toolchain binaries') - parser.add_argument('-i', '--mmu', action='store', required=False, help='MMU build options') + parser = argparse.ArgumentParser( + description="Report the different segment sizes of a compiled ELF file" + ) + parser.add_argument( + "-e", + "--elf", + action="store", + required=True, + help="Path to the Arduino sketch ELF", + ) + parser.add_argument( + "-p", + "--path", + action="store", + required=True, + help="Path to Xtensa toolchain binaries", + ) + parser.add_argument( + "-i", "--mmu", action="store", required=False, help="MMU build options" + ) args = parser.parse_args() - sizes = get_segment_sizes(args.elf, args.path) - [iram, sizes] = get_mmu_sizes(args.mmu, sizes) - hints = get_segment_hints(iram) + sizes = get_segment_sizes(args.elf, args.path, args.mmu) + hints = get_segment_hints() - sys.stderr.write("Executable segment sizes:" + os.linesep) - for k in sizes.keys(): - sys.stderr.write("%-7s: %-5d %s %s" % (k, sizes[k], hints[k], os.linesep)) + template = "{:<8} {:<8} {:<8} {:<8} {:<16}" + header = template.format("SEGMENT", "USED", "TOTAL", "PERCENT", "DESCRIPTION") + + with contextlib.redirect_stdout(sys.stderr): + print(header) + print(len(header) * "-") + for key, (used, total) in sizes.items(): + print( + template.format(key, used, total, percentage(used, total), hints[key]) + ) return 0 -if __name__ == '__main__': - sys.exit(main()) + +if __name__ == "__main__": + main() From 130a9955f073cf9e4ac85eb3b95b74f00b3e694c Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Sat, 30 Apr 2022 09:02:00 +0300 Subject: [PATCH 2/5] revert to line output --- tools/sizes.py | 104 +++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/tools/sizes.py b/tools/sizes.py index d5e737279f..9abf1a91a8 100755 --- a/tools/sizes.py +++ b/tools/sizes.py @@ -24,49 +24,64 @@ import contextlib -def get_segment_hints(): - return { - "ICACHE": "flash instruction cache", - "IROM": "code in flash (default, ICACHE_FLASH_ATTR)", - "IRAM": "code in IRAM (IRAM_ATTR, ICACHE_RAM_ATTR)", - "DATA": "initialized variables (global, static) in RAM", - "RODATA": "constants (global, static) in RAM", - "BSS": "zeroed variables (global, static) in RAM", - } +HINTS = { + "ICACHE": "configured flash instruction cache", + "IROM": "code in flash (default, ICACHE_FLASH_ATTR)", + "IRAM": "code in IRAM (IRAM_ATTR, ICACHE_RAM_ATTR)", + "DATA": "initialized variables (global, static)", + "RODATA": "constants (global, static)", + "BSS": "zeroed variables (global, static)", +} def get_segment_sizes(elf, path, mmu): - sizes = { - "ICACHE": [32768, 32768], - "IROM": [0, 1048576], - "IRAM": [0, 32768], - "DATA": [0, 81920], - "RODATA": [0, 81920], - "BSS": [0, 81920], - } + iram_size = 0 + icache_size = 32168 + + for line in mmu.split(): + words = line.split("=") + if line.startswith("-DMMU_IRAM_SIZE"): + iram_size = int(words[1], 16) + elif line.startswith("-DMMU_ICACHE_SIZE"): + icache_size = int(words[1], 16) + + sizes = [ + ["Variables and constants in RAM", [{ + "DATA": 0, + "RODATA": 0, + "BSS": 0, + }, 80192]], + ["Instruction cache", [{ + "ICACHE": icache_size, + }, icache_size]], + ["Instruction RAM", [{ + "IRAM": 0, + }, iram_size]], + ["Code in flash", [{ + "IROM": 0 + }, 1048576]], + ] + + mapping = [ + [".irom0.text", "IROM"], + [".text", "IRAM"], + [".data", "DATA"], + [".rodata", "RODATA"], + [".bss", "BSS"], + ] cmd = [os.path.join(path, "xtensa-lx106-elf-size"), "-A", elf] with subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) as proc: lines = proc.stdout.readlines() for line in lines: words = line.split() - if line.startswith(".irom0.text"): - sizes["IROM"][0] += int(words[1]) - elif line.startswith(".text"): - sizes["IRAM"][0] += int(words[1]) - elif line.startswith(".data"): - sizes["DATA"][0] += int(words[1]) - elif line.startswith(".rodata"): - sizes["RODATA"][0] += int(words[1]) - elif line.startswith(".bss"): - sizes["BSS"][0] += int(words[1]) - - for line in mmu.split(): - words = line.split("=") - if line.startswith("-DMMU_IRAM_SIZE"): - sizes["IRAM"][1] = int(words[1], 16) - elif line.startswith("-DMMU_ICACHE_SIZE"): - sizes["ICACHE"][0] = sizes["ICACHE"][1] = int(words[1], 16) + for section, target in mapping: + if not line.startswith(section): + continue + for group, (segments, total) in sizes: + if target in segments: + segments[target] += int(words[1]) + assert segments[target] <= total return sizes @@ -99,20 +114,15 @@ def main(): args = parser.parse_args() sizes = get_segment_sizes(args.elf, args.path, args.mmu) - hints = get_segment_hints() - - template = "{:<8} {:<8} {:<8} {:<8} {:<16}" - header = template.format("SEGMENT", "USED", "TOTAL", "PERCENT", "DESCRIPTION") - - with contextlib.redirect_stdout(sys.stderr): - print(header) - print(len(header) * "-") - for key, (used, total) in sizes.items(): - print( - template.format(key, used, total, percentage(used, total), hints[key]) - ) - return 0 + for group, (segments, total) in sizes: + print(f". {group:<8} (total {total} bytes)") + for n, (segment, size) in enumerate(segments.items(), start=1): + if n == len(segments): + prefix = "└──" + else: + prefix = "├──" + print(f"{prefix} {segment:<8} {size:<8} - {HINTS[segment]:<16}") if __name__ == "__main__": From 38886eac1f8a104a1788000cb01b087d4678e4ce Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Mon, 2 May 2022 13:47:38 +0300 Subject: [PATCH 3/5] used, percentage --- tools/sizes.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tools/sizes.py b/tools/sizes.py index 9abf1a91a8..4559bb7efc 100755 --- a/tools/sizes.py +++ b/tools/sizes.py @@ -25,9 +25,9 @@ HINTS = { - "ICACHE": "configured flash instruction cache", - "IROM": "code in flash (default, ICACHE_FLASH_ATTR)", + "ICACHE": "reserved space for flash instruction cache", "IRAM": "code in IRAM (IRAM_ATTR, ICACHE_RAM_ATTR)", + "IROM": "code in flash (default, ICACHE_FLASH_ATTR)", "DATA": "initialized variables (global, static)", "RODATA": "constants (global, static)", "BSS": "zeroed variables (global, static)", @@ -51,12 +51,10 @@ def get_segment_sizes(elf, path, mmu): "RODATA": 0, "BSS": 0, }, 80192]], - ["Instruction cache", [{ - "ICACHE": icache_size, - }, icache_size]], ["Instruction RAM", [{ + "ICACHE": icache_size, "IRAM": 0, - }, iram_size]], + }, 65536]], ["Code in flash", [{ "IROM": 0 }, 1048576]], @@ -116,13 +114,14 @@ def main(): sizes = get_segment_sizes(args.elf, args.path, args.mmu) for group, (segments, total) in sizes: - print(f". {group:<8} (total {total} bytes)") + used = sum(segments.values()) + print(f". {group:<8}, total {total} bytes, used {used} bytes ({percentage(used, total)})") for n, (segment, size) in enumerate(segments.items(), start=1): if n == len(segments): prefix = "└──" else: prefix = "├──" - print(f"{prefix} {segment:<8} {size:<8} - {HINTS[segment]:<16}") + print(f"{prefix} {segment:<8} {size:<8} {HINTS[segment]:<16}") if __name__ == "__main__": From 9a399b9024b54afdd5f16eba9e1a070f4f78d0b1 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Mon, 2 May 2022 14:15:04 +0300 Subject: [PATCH 4/5] unicodes --- tools/sizes.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/tools/sizes.py b/tools/sizes.py index 4559bb7efc..7d2929483f 100755 --- a/tools/sizes.py +++ b/tools/sizes.py @@ -23,7 +23,6 @@ import sys import contextlib - HINTS = { "ICACHE": "reserved space for flash instruction cache", "IRAM": "code in IRAM (IRAM_ATTR, ICACHE_RAM_ATTR)", @@ -88,6 +87,24 @@ def percentage(lhs, rhs): return "{}%".format(int(100.0 * float(lhs) / float(rhs))) +def prefix(n, segments): + if n == len(segments): + out = "└──" + else: + out = "├──" + + return out + + +def safe_prefix(n, segments): + if n == len(segments): + out = "|__" + else: + out = "|--" + + return out + + def main(): parser = argparse.ArgumentParser( description="Report the different segment sizes of a compiled ELF file" @@ -115,13 +132,18 @@ def main(): for group, (segments, total) in sizes: used = sum(segments.values()) - print(f". {group:<8}, total {total} bytes, used {used} bytes ({percentage(used, total)})") + print( + f". {group:<8}, total {total} bytes, used {used} bytes ({percentage(used, total)})" + ) for n, (segment, size) in enumerate(segments.items(), start=1): - if n == len(segments): - prefix = "└──" - else: - prefix = "├──" - print(f"{prefix} {segment:<8} {size:<8} {HINTS[segment]:<16}") + try: + print( + f"{prefix(n, segments)} {segment:<8} {size:<8} {HINTS[segment]:<16}" + ) + except UnicodeEncodeError: + print( + f"{safe_prefix(n, segments)} {segment:<8} {size:<8} {HINTS[segment]:<16}" + ) if __name__ == "__main__": From 818ff2ec8a06770d5c051bbd5d3fbe34a323167a Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Thu, 5 May 2022 16:15:31 +0300 Subject: [PATCH 5/5] shorter desc, headers --- tools/sizes.py | 130 +++++++++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 54 deletions(-) diff --git a/tools/sizes.py b/tools/sizes.py index 7d2929483f..ee7219161a 100755 --- a/tools/sizes.py +++ b/tools/sizes.py @@ -20,21 +20,11 @@ import argparse import os import subprocess -import sys -import contextlib - -HINTS = { - "ICACHE": "reserved space for flash instruction cache", - "IRAM": "code in IRAM (IRAM_ATTR, ICACHE_RAM_ATTR)", - "IROM": "code in flash (default, ICACHE_FLASH_ATTR)", - "DATA": "initialized variables (global, static)", - "RODATA": "constants (global, static)", - "BSS": "zeroed variables (global, static)", -} def get_segment_sizes(elf, path, mmu): iram_size = 0 + iheap_size = 0 icache_size = 32168 for line in mmu.split(): @@ -43,36 +33,49 @@ def get_segment_sizes(elf, path, mmu): iram_size = int(words[1], 16) elif line.startswith("-DMMU_ICACHE_SIZE"): icache_size = int(words[1], 16) + elif line.startswith("-DMMU_SEC_HEAP_SIZE"): + iheap_size = int(words[1], 16) sizes = [ - ["Variables and constants in RAM", [{ - "DATA": 0, - "RODATA": 0, - "BSS": 0, - }, 80192]], - ["Instruction RAM", [{ - "ICACHE": icache_size, - "IRAM": 0, - }, 65536]], - ["Code in flash", [{ - "IROM": 0 - }, 1048576]], + [ + "Variables and constants in RAM (global, static)", + [ + { + "DATA": 0, + "RODATA": 0, + "BSS": 0, + }, + 80192, + ], + ], + [ + "Instruction RAM (IRAM_ATTR, ICACHE_RAM_ATTR)", + [ + { + "ICACHE": icache_size, + "IHEAP": iheap_size, + "IRAM": 0, + }, + 65536, + ], + ], + ["Code in flash (default, ICACHE_FLASH_ATTR)", [{"IROM": 0}, 1048576]], ] - mapping = [ - [".irom0.text", "IROM"], - [".text", "IRAM"], - [".data", "DATA"], - [".rodata", "RODATA"], - [".bss", "BSS"], - ] + section_mapping = ( + (".irom0.text", "IROM"), + (".text", "IRAM"), + (".data", "DATA"), + (".rodata", "RODATA"), + (".bss", "BSS"), + ) cmd = [os.path.join(path, "xtensa-lx106-elf-size"), "-A", elf] with subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) as proc: lines = proc.stdout.readlines() for line in lines: words = line.split() - for section, target in mapping: + for section, target in section_mapping: if not line.startswith(section): continue for group, (segments, total) in sizes: @@ -87,22 +90,44 @@ def percentage(lhs, rhs): return "{}%".format(int(100.0 * float(lhs) / float(rhs))) -def prefix(n, segments): - if n == len(segments): - out = "└──" - else: - out = "├──" +HINTS = { + "ICACHE": "reserved space for flash instruction cache", + "IRAM": "code in IRAM", + "IHEAP": "secondary heap space", + "IROM": "code in flash", + "DATA": "initialized variables", + "RODATA": "constants", + "BSS": "zeroed variables", +} + + +def safe_prefix(n, length): + if n == length: + return "`--" + + return "|--" - return out +def prefix(n, length): + if n == length: + return "└──" -def safe_prefix(n, segments): - if n == len(segments): - out = "|__" - else: - out = "|--" + return "├──" - return out + +def filter_segments(segments): + used = 0 + number = 0 + available = [] + + for (segment, size) in segments.items(): + if not size: + continue + used += size + number += 1 + available.append((number, segment, size)) + + return (number, used, available) def main(): @@ -131,19 +156,16 @@ def main(): sizes = get_segment_sizes(args.elf, args.path, args.mmu) for group, (segments, total) in sizes: - used = sum(segments.values()) - print( - f". {group:<8}, total {total} bytes, used {used} bytes ({percentage(used, total)})" - ) - for n, (segment, size) in enumerate(segments.items(), start=1): + number, used, segments = filter_segments(segments) + + print(f". {group:<8}, used {used} / {total} bytes ({percentage(used, total)})") + print("| SEGMENT BYTES DESCRIPTION") + for n, segment, size in segments: try: - print( - f"{prefix(n, segments)} {segment:<8} {size:<8} {HINTS[segment]:<16}" - ) + print(f"{prefix(n, number)} ", end="") except UnicodeEncodeError: - print( - f"{safe_prefix(n, segments)} {segment:<8} {size:<8} {HINTS[segment]:<16}" - ) + print(f"{safe_prefix(n, number)} ", end="") + print(f"{segment:<8} {size:<8} {HINTS[segment]:<16}") if __name__ == "__main__":