Skip to content
9 changes: 9 additions & 0 deletions platform/arm-gcc-m55.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
set(CMAKE_SYSTEM_NAME Generic)

set(CMAKE_C_COMPILER arm-none-eabi-gcc)
# CMAKE_SIZE_UTIL is not a 'real' CMake variable but is sometimes
# used by convention in embedded toolchain files.
set(CMAKE_SIZE_UTIL arm-none-eabi-size)

set(CMAKE_C_FLAGS "-mthumb -mcpu=cortex-m55 -Os")
set(CMAKE_EXE_LINKER_FLAGS "--specs=nosys.specs")
89 changes: 89 additions & 0 deletions scripts/diff_code_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Examine 2 code size reports and print the difference between them.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's your typical workflow with this script?

I use mbedtls-size-dwim, where my workflow is to set up one or more build directories and run e.g. mbedtls-size-dwim build-m0plus each time I commit and at any point while I'm developing. It shows the code size differences with the last commit.

With diff_code_size.py, where would I typically find the files to compare?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If working in conjunction with Mbed-TLS/TF-PSA-Crypto#590 it would be something like:

# Build version A
cmake -B build-A .
cmake --build build-A --target size # or (cd build-A && make size)
# Build version B
cmake -B build-B .
cmake --build build-B --target size # or (cd build-B && make size)

# Compare code size
framework/scripts/diff_code_size.py build-A/core/code_size.json build-B/core/code_size.json

It might be easier if it found the code size report itself, should I add that feature?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok, you're typically comparing different build options? I find that I mostly want to compare different versions of the same code. That's why mbedtls-size-dwim stores sizes in a file named after the git head sha.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually I need to compare different options but assessing the code size impact of a config option happens often enough that I value the flexibility.

"""

# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

import json
import os
import sys

def load_sizes(json_file):
with open(json_file) as f:
json_sizes = f.read()

sizes = json.loads(json_sizes)
return sizes

def generate_diff_table(sizes_a, sizes_b):
table = []
total_size_a = 0
total_size_b = 0

for file in sizes_a:
size_a = (sizes_a[file]['text']
+ sizes_a[file]['data']
+ sizes_a[file]['bss'])
total_size_a += size_a

if file in sizes_b:
size_b = (sizes_b[file]['text']
+ sizes_b[file]['data']
+ sizes_b[file]['bss'])
size_diff = size_b - size_a

if size_diff != 0:
table.append((file.split('.')[0], size_a, size_b, size_diff,
(size_diff * 100.0 / size_a)))
else:
# This file is only in A, so there's a code size decrease
table.append((file.split('.')[0], size_a, 0, -size_a, -100.0))

for file in sizes_b:
size_b = (sizes_b[file]['text']
+ sizes_b[file]['data']
+ sizes_b[file]['bss'])
total_size_b += size_b

if file not in sizes_a:
# This file is only in B, so there's a code size increase
table.append((file.split('.')[0], 0, size_b, size_b, 100.0))

total_size_diff = total_size_b - total_size_a
table.append(('TOTAL', total_size_a, total_size_b, total_size_diff,
(total_size_diff * 100.0 / total_size_a)))

return table

def display_diff_table(table):
table_line_format = '{:<40} {:>8} {:>8} {:>+8} {:>+8.2f}%'

print('{:<40} {:>8} {:>8} {:>8} {:>9}'.format(
'Module', 'Old', 'New', 'Delta', '% Delta'))

for line in table:
print(table_line_format.format(*line))

def main():
if len(sys.argv) < 3:
print('Error: Less than 2 JSON files / build directories supplied.', file=sys.stderr)
sys.exit(1)

file_a = sys.argv[1]
file_b = sys.argv[2]

# If the arguments are build directories, find the JSON
# code-size report in core/code_size.json
if os.path.isdir(file_a):
file_a = file_a + '/core/code_size.json'
if os.path.isdir(file_b):
file_b = file_b + '/core/code_size.json'

sizes_a = load_sizes(file_a)
sizes_b = load_sizes(file_b)

display_diff_table(generate_diff_table(sizes_a, sizes_b))

if __name__ == '__main__':
main()
67 changes: 67 additions & 0 deletions scripts/generate_code_size_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Generate a code size report for the supplied library file, using the given
size tool, and write it in JSON format to the given output file.
"""

# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

import argparse
import json
import subprocess
import sys

def get_size_output(size_cmd: str, library_file: str):
"""Run the size command on the library and get its output"""
output = subprocess.check_output([size_cmd, library_file])
return str(output, 'UTF-8')

def size_breakdown(size_output: str):
"""Convert the output of the size command to a dictionary like the following:
{'filename.c.obj' : {'text': 123, 'data': 456, 'bss': 789}}"""

sizes = {}

for line in size_output.splitlines()[1:]:
row = line.split()
obj_file_name = row[5]
text_size = int(row[0])
data_size = int(row[1])
bss_size = int(row[2])

sizes[obj_file_name] = {'text': text_size, 'data': data_size, 'bss': bss_size}

return sizes

def write_out_results(sizes: dict, output_file: str):
"""Write out the sizes in JSON to the output file"""
sizes_json = json.dumps(sizes, indent=4)

with open(output_file, "w") as out:
out.write(sizes_json)

def main():
parser = argparse.ArgumentParser(
description='Generate a code size report in JSON format')

parser.add_argument("-o", "--output-file",
help="Filename of the report to generate",
default='code_size.json')

parser.add_argument("-s", "--size-cmd",
help="Size command to use (e.g. arm-none-eabi-size)",
required=True)

parser.add_argument("-l", "--library-file",
help="Library file to generate report from",
required=True)

args = parser.parse_args()

sizes = size_breakdown(get_size_output(args.size_cmd, args.library_file))

write_out_results(sizes, args.output_file)

if __name__ == "__main__":
main()

56 changes: 56 additions & 0 deletions scripts/get_code_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Build the library for Cortex-M55 using the toolchain file in
framework/platform and measure and print the code size using the
existing scripts.
"""

# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

import argparse
import json
import os
import subprocess

def build_library(build_dir, toolchain_file, named_config):
config_file_path = build_dir + '/code_size_crypto_config.h'

subprocess.check_call(['cp', 'include/psa/crypto_config.h', config_file_path])
subprocess.check_call(['scripts/config.py', '-f', config_file_path, named_config])
subprocess.check_call(['cmake', '.', '-B' + build_dir,
'-DCMAKE_TOOLCHAIN_FILE=' + toolchain_file,
'-DENABLE_PROGRAMS=NO',
'-DTF_PSA_CRYPTO_CONFIG_FILE=' + config_file_path])
subprocess.check_call(['cmake', '--build', build_dir, '-j' + str(os.cpu_count())])

def generate_sizes(build_dir, size_cmd):
subprocess.check_call(['framework/scripts/generate_code_size_report.py',
'--output-file', build_dir + '/code_size.json',
'--library-file', build_dir + '/core/libtfpsacrypto.a',
'--size-cmd', size_cmd])

def display_sizes(build_dir):
subprocess.check_call(['framework/scripts/show_code_size.py',
build_dir + '/code_size.json'])

if __name__ == '__main__':
BUILD_DIR = 'build-code-size'

parser = argparse.ArgumentParser(
description='Generate a code size report in JSON format')

parser.add_argument('-s', '--size-cmd',
help='Size command to use (default arm-none-eabi-size)',
default='arm-none-eabi-size')
parser.add_argument('-t', '--toolchain-file',
help='CMake toolchain file to use for building',
default='framework/platform/arm-gcc-m55.cmake')
parser.add_argument('-c', '--config-name',
help='Named config to use for size measurement.',
default='baremetal_size')

args = parser.parse_args()

build_library(BUILD_DIR, args.toolchain_file, args.config_name)
generate_sizes(BUILD_DIR, args.size_cmd)
display_sizes(BUILD_DIR)
45 changes: 45 additions & 0 deletions scripts/show_code_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Parse a JSON code size report and output a pretty-printed table of sizes
for each module along with their totals.
"""

# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

import json
import sys

def display_sizes(json_file):
with open(json_file) as f:
json_sizes = f.read()

sizes = json.loads(json_sizes)

padded_line = '{:<40} {:>8} {:>8} {:>8} {:>8}'

print(padded_line.format('file', 'text', 'data', 'bss', 'total'))

text_total = 0
data_total = 0
bss_total = 0
total_total = 0

for file in sizes:
module = file.split('.')[0] # Remove .c.obj extension
text = sizes[file]['text']
data = sizes[file]['data']
bss = sizes[file]['bss']
print(padded_line.format(module, text, data, bss, text + data + bss))
text_total += text
data_total += data
bss_total += bss
total_total += text + data + bss

print(padded_line.format('TOTAL', text_total, data_total, bss_total, total_total))

if __name__ == '__main__':
if len(sys.argv) < 2:
print('Error: No JSON file supplied.', file=sys.stderr)
sys.exit(1)

display_sizes(sys.argv[1])