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
+ );
+ }
+}