Skip to content

Commit

Permalink
Add a tool that prints information on the sizes of the examples.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jrvanwhy committed Jun 16, 2020
1 parent c0d2b6f commit 24f7f6b
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 3 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ exclude = [ "tock" ]
members = [
"codegen",
"core",
"test-runner"
"test-runner",
"tools/print-sizes",
]
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ usage:
@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)
Expand Down Expand Up @@ -53,6 +54,11 @@ kernel-hifive:
$(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
Expand All @@ -62,11 +68,12 @@ test-qemu-hifive: kernel-hifive setup-qemu

.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
Expand Down
14 changes: 14 additions & 0 deletions core/examples/empty_main.rs
Original file line number Diff line number Diff line change
@@ -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() {}
13 changes: 13 additions & 0 deletions tools/print-sizes/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
149 changes: 149 additions & 0 deletions tools/print-sizes/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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
);
}
}

0 comments on commit 24f7f6b

Please sign in to comment.