Add a tool that prints information on the sizes of the examples. The tool prints the size of .bss, .data, and .text (which currently includes .rodata). I tried to adjust the linker script to separate out .rodata, but unfortunately that caused RISC-V apps to fault. I'll take care of that in a later PR, when I refactor the entry point and layout script. I intend to develop a GitHub Action similar to tock/tock's that runs print-sizes on incoming PRs and displays the diff relative to the PR's target branch.
diff --git a/Cargo.toml b/Cargo.toml index 0016e7a..b8e0bec 100644 --- a/Cargo.toml +++ b/Cargo.toml
@@ -62,5 +62,6 @@ members = [ "codegen", "core", - "test-runner" + "test-runner", + "tools/print-sizes", ]
diff --git a/Makefile b/Makefile index cf1a305..d205cc7 100644 --- a/Makefile +++ b/Makefile
@@ -22,6 +22,7 @@ @echo " Set the FEATURES flag to enable features" @echo "Run 'make flash-<board> EXAMPLE=<>' to flash EXAMPLE to that board" @echo "Run 'make test' to test any local changes you have made" + @echo "Run 'make print-sizes' to print size data for the example binaries" ifdef FEATURES features=--features=$(FEATURES) @@ -53,6 +54,11 @@ $(MAKE) -C tock/boards/hifive1 \ $(CURDIR)/tock/target/riscv32imac-unknown-none-elf/release/hifive1.elf +# Prints out the sizes of the example binaries. +.PHONY: print-sizes +print-sizes: examples + cargo run --release -p print-sizes + # Runs the libtock_test tests in QEMU on a simulated HiFive board. .PHONY: test-qemu-hifive test-qemu-hifive: kernel-hifive setup-qemu @@ -62,11 +68,12 @@ .PHONY: examples examples: - PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --examples + PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --examples -p libtock -p libtock-core PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --examples --features=alloc PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --example panic --features=custom_panic_handler,custom_alloc_error_handler PLATFORM=nrf52 cargo build --release --target=thumbv7em-none-eabi --example alloc_error --features=alloc,custom_alloc_error_handler - PLATFORM=opentitan cargo build --release --target=riscv32imc-unknown-none-elf --examples # Important: This is testing a platform without atomics support + # Important: This tests a platform without atomic instructions. + PLATFORM=opentitan cargo build --release --target=riscv32imc-unknown-none-elf --examples -p libtock -p libtock-core .PHONY: test test: examples test-qemu-hifive
diff --git a/core/examples/empty_main.rs b/core/examples/empty_main.rs new file mode 100644 index 0000000..dd062bc --- /dev/null +++ b/core/examples/empty_main.rs
@@ -0,0 +1,14 @@ +// The most minimal libtock-core example possible. This file primarily exists +// for code size measurement, as this should create the smallest-possible +// libtock-core app. + +#![no_std] + +// If you don't *use* anything from libtock-core directly, cargo will not link +// it into the executable. However, we still need the runtime and lang items. +// Therefore a libtock-core app that doesn't directly mention anything in +// libtock-core needs to explicitly declare its dependency on libtock-core as +// follows. +extern crate libtock_core; + +fn main() {}
diff --git a/tools/print-sizes/Cargo.toml b/tools/print-sizes/Cargo.toml new file mode 100644 index 0000000..97bdc48 --- /dev/null +++ b/tools/print-sizes/Cargo.toml
@@ -0,0 +1,13 @@ +# Finds all the libtock-core and libtock-rs examples and prints the sizes of +# several of their sections. Searches the target/$ARCH/release directory. Note +# that print-sizes will not build the examples; that is done by the +# `print-sizes` Makefile action. + +[package] +authors = ["Tock Project Developers <tock-dev@googlegroups.com>"] +edition = "2018" +name = "print-sizes" +version = "0.1.0" + +[dependencies] +elf = "0.0.10"
diff --git a/tools/print-sizes/src/main.rs b/tools/print-sizes/src/main.rs new file mode 100644 index 0000000..dcd7777 --- /dev/null +++ b/tools/print-sizes/src/main.rs
@@ -0,0 +1,142 @@ +// Architectures that we expect the examples to be built for. +const ARCHITECTURES: [&str; 2] = ["riscv32imc-unknown-none-elf", "thumbv7em-none-eabi"]; + +// The order of these fields actually matters, because it affects the derived +// Ord impl. I have a suspicion that when I introduce size diffs into the CI, +// this order will make the eventual diffs easier to understand than other +// orderings. +#[derive(Eq, PartialEq, PartialOrd, Ord)] +struct Example { + name: String, + arch: &'static str, + path: std::path::PathBuf, +} + +// Finds the example binaries and returns a list of their paths. +fn find_examples() -> Vec<Example> { + // Find target/ using std::env::current_exe(). + let exe_dir = std::env::current_exe().expect("Unable to find executable location"); + let target_dir = exe_dir + .parent() + .expect("Unable to find target/ directory") + .parent() + .expect("Unable to find target/ directory"); + + let mut examples = Vec::new(); + + for arch in &ARCHITECTURES { + // Set examples_dir to target/$ARCH/examples/ + let mut examples_dir = target_dir.to_path_buf(); + examples_dir.push(arch); + examples_dir.push("release"); + examples_dir.push("examples"); + + // If the architecture's examples directory exists, iterate through the + // files through it and search for examples. If the directory doesn't + // exist we skip this architecture. + if let Ok(read_dir) = examples_dir.read_dir() { + for file in read_dir.filter_map(Result::ok) { + use std::os::unix::ffi::OsStrExt; + + // Skip entries that are not files. If file_type() returns + // Err(_) we skip the entry as well. + if !file.file_type().map_or(false, |t| t.is_file()) { + continue; + } + + // Skip files with dots (*.d files) and hyphens (-$HASH) in + // them. + if file.file_name().as_bytes().contains(&b'.') + || file.file_name().as_bytes().contains(&b'-') + { + continue; + } + + examples.push(Example { + name: file.file_name().to_string_lossy().into_owned(), + arch, + path: file.path(), + }); + } + } + } + + examples +} + +struct ElfSizes { + bss: u64, + data: u64, + rodata: u64, + text: u64, +} + +fn get_sizes(path: &std::path::Path) -> ElfSizes { + let file = elf::File::open_path(path).expect("Unable to open example binary"); + let mut sizes = ElfSizes { + bss: 0, + data: 0, + rodata: 0, + text: 0, + }; + for section in file.sections { + match section.shdr.name.as_ref() { + ".bss" => sizes.bss = section.shdr.size, + ".data" => sizes.data = section.shdr.size, + ".rodata" => sizes.rodata = section.shdr.size, + ".text" => sizes.text = section.shdr.size, + _ => {} + } + } + sizes +} + +struct ExampleData { + name: String, + arch: &'static str, + sizes: ElfSizes, +} + +fn main() { + let mut examples = find_examples(); + examples.sort_unstable(); + let example_data: Vec<_> = examples + .drain(..) + .map(|example| ExampleData { + name: example.name, + arch: example.arch, + sizes: get_sizes(&example.path), + }) + .collect(); + + let name_width = 20; + let arch_width = example_data + .iter() + .map(|a| a.arch.len()) + .max() + .expect("No examples found"); + let section_width = 7; + + // TODO: We do not currently print out .rodata's size. Currently, the linker + // script embeds .rodata in .text, so we don't see it as a separate section + // here. We should modify the linker script to put .rodata in its own + // section. Until that is done, .rodata's size will be counted as part of + // .text, so we'll just print .text's size for now. + println!( + "{0:1$} {2:3$} {4:>7$} {5:>7$} {6:>7$}", + "Example", name_width, "Architecture", arch_width, ".bss", ".data", ".text", section_width + ); + for data in example_data { + println!( + "{0:1$} {2:3$} {4:7$} {5:7$} {6:7$}", + data.name, + name_width, + data.arch, + arch_width, + data.sizes.bss, + data.sizes.data, + data.sizes.text, + section_width + ); + } +}