I'm making a library for Arduino Uno in Rust from scratch. Currently, there's a basic Serial.write print example in the src/main.rs, which on compilation is around 500 bytes (cargo b --release).
$ avr-size target/atmega328p/release/rustymetal.elf # that's the name of bin
text data bss dec hex filename
488 24 1 513 201 target/atmega328p/release/rustymetal.elf
# ^^^ this number is the total size
Which I'd like to port to examples/serial-print.rs, so that I could in future add more examples in different files rather in a single main.rs.
BUT, the same example merely copy-pasted into examples/serial_print.rs directory and compiling via cargo b --example serial-print --release blows up the size immensely, to nearly 10000 bytes!
$ avr-size target/atmega328p/release/examples/serial-print.elf
text data bss dec hex filename
7972 2922 1 10895 2a8f target/atmega328p/release/examples/serial-print.elf
I checked out the cargo bloat output for both, and it was pretty evident there's some kind of dead-code elimination that's NOT happening for the example version (LTO is on for both though):
Here's the output for src/main.rs:
$ cargo bloat --release # this generates the output for bin
Analyzing target/atmega328p/release/rustymetal.elf
File .text Size Crate Name
3.6% 61.9% 302B [Unknown] main
0.2% 2.9% 14B smolduino __vector_22
0.0% 0.8% 4B core core::result::unwrap_failed
0.0% 0.4% 2B core core::panicking::panic_fmt
5.7% 100.0% 488B .text section size, the file size is 8.3KiB
Warning: it seems like the `.text` section is nearly empty. Try removing `strip = true` from Cargo.toml
And here's the output for examples/serial-print.rs(!):
$ cargo bloat --example serial-print --release
Analyzing target/atmega328p/release/examples/serial-print.elf
File .text Size Crate Name
6.7% 21.2% 1.7KiB core core::char::methods::<impl char>::escape_debug_ext
3.1% 9.7% 772B core <core::fmt::builders::PadAdapter as core::fmt::Write>::write_str
2.6% 8.2% 650B core2 <&T as core::fmt::Debug>::fmt
2.5% 8.0% 634B core core::fmt::write
2.0% 6.4% 510B core core::escape::EscapeIterInner<_>::unicode
1.7% 5.5% 436B core core::fmt::builders::DebugStruct::field_with
1.5% 4.8% 384B core core::fmt::builders::DebugTuple::field_with
1.4% 4.4% 354B core <core::str::iter::Chars as core::iter::traits::iterator::Iterator>::next
1.3% 4.2% 332B core2 <core2::io::error::Error as core::fmt::Debug>::fmt
1.3% 4.1% 330B [Unknown] main
1.1% 3.5% 278B core core::unicode::printable::check
1.0% 3.1% 248B core core::str::slice_error_fail_rt
0.7% 2.1% 164B smolduino? <smolduino::error::Error as core::fmt::Debug>::fmt
0.6% 1.9% 152B core core::fmt::builders::DebugTuple::finish
0.6% 1.7% 138B core <core::fmt::builders::PadAdapter as core::fmt::Write>::write_char
0.4% 1.1% 90B core core::str::<impl str>::floor_char_boundary
0.3% 1.1% 86B core core::str::traits::<impl core::ops::index::Index<I> for str>::index
0.3% 1.0% 82B core <core::char::EscapeDebug as core::fmt::Display>::fmt
0.2% 0.8% 60B core core::fmt::Arguments::as_statically_known_str
0.2% 0.7% 58B core core::fmt::Write::write_fmt
1.4% 4.5% 356B And 18 smaller methods. Use -n N to show more.
31.8% 100.0% 7.8KiB .text section size, the file size is 24.5KiB
For completeness' sake, here's Cargo.toml, src/main.rs and examples/serial-print.rs:
Cargo.toml:
[package]
name = "smolduino"
version = "0.1.0"
edition = "2021"
[lib]
name = "smolduino"
path = "src/lib.rs"
# I'm just doing this to remove the unnecessary error by Rust-analyzer
# I do not need tests rn, but might have to find an alterative soon when I do..
test = false
bench = false
[[bin]]
name = "rustymetal"
path = "src/main.rs"
# I'm just doing this to remove the unnecessary error by Rust-analyzer
# I do not need tests rn, but might have to find an alterative soon when I do..
test = false
bench = false
[[example]]
name = "serial-print"
path = "examples/serial-print.rs"
[profile.dev]
codegen-units = 1
panic = "abort"
opt-level = "z"
lto = true
# strip = true
[profile.release]
codegen-units = 1
panic = "abort"
opt-level = "z"
lto = true
strip = true
[dependencies]
avrd = { version = "1.0.0" }
core2 = { version = "0.4.0", default-features = false, features = ["nightly"] }
paste = "1.0.15"
src/main.rs:
#![no_std]
#![no_main]
#![feature(asm_experimental_arch)]
#![feature(abi_avr_interrupt)]
pub mod error;
pub mod io;
pub mod sync;
pub mod sys;
pub mod timing;
pub mod utils;
use core::{panic::PanicInfo, time::Duration};
use core2::io::Write;
use io::serial::Serial;
use sys::interrupt;
use timing::delay;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
extern "C" fn main() -> ! {
unsafe {
interrupt::enable_intr();
}
let mut serial = Serial::with_baud_rate(9600).unwrap();
loop {
// Rust flex, UTF-8 text now works on Arduino!
serial.write_all("नमस्ते ॐ\n".as_bytes()).unwrap();
delay::delay(Duration::from_secs(5));
}
}
examples/serial-print.rs (as you can see it's literal copy-paste, with import statements adjusted):
#![no_std]
#![no_main]
use core::{panic::PanicInfo, time::Duration};
use core2::io::Write;
use smolduino::io::serial::Serial;
use smolduino::sys::interrupt;
use smolduino::timing::delay;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
extern "C" fn main() -> ! {
unsafe {
interrupt::enable_intr();
}
let mut serial = Serial::with_baud_rate(9600).unwrap();
loop {
// Rust flex, UTF-8 text now works on Arduino!
serial.write_all("नमस्ते ॐ\n".as_bytes()).unwrap();
delay::delay(Duration::from_secs(5));
}
}
Here's the target .json atmega328p.json:
{
"arch": "avr",
"atomic-cas": false,
"cpu": "atmega328p",
"data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
"eh-frame-header": false,
"exe-suffix": ".elf",
"late-link-args": {
"gcc": [
"-lgcc"
]
},
"linker": "avr-gcc",
"llvm-target": "avr-unknown-unknown",
"max-atomic-width": 8,
"no-default-libraries": false,
"pre-link-args": {
"gcc": [
"-mmcu=atmega328p"
]
},
"relocation-model": "static",
"target-c-int-width": "16",
"target-pointer-width": "16"
}
It is worth mentioning, that for AVR targets, you need to be on nightly.
EDIT: As requested, here's the memory map (by avr-ld) for the binary output,
and here's the memory map (by avr-ld) for the example output.
EDIT 2: Hmm.. it doesn't seem to be an issue with avr-ld. All the same arguments are being passed to avr-ld in both cases, the issue is originating from Rust/LLVM side I guess.
-Map <file>.