rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.34k stars 12.72k forks source link

[AVR] core::fmt::Write is broken without lto #107497

Closed commonkestrel closed 11 months ago

commonkestrel commented 1 year ago

I tried this code:

struct Serial {}

/* transmit() and begin() defined here */

impl Write for Serial {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        for c in s.chars() {
            Self::transmit(c as u8); // Transmits byte via serial on ATmega328p
        }
        Ok(())
    }
}

pub extern "C" fn main() {
     let serial = Serial::begin(57600);
     serial.write_fmt(format_args!("hello world\n")); // Prints properly and returns Ok variant, but crashes if unwrap is called
     serial.write_fmt(format_args!("{}\n", 3); // Crashes without unwrap() after build and upload but before program executes

}

/* panic and eh_personality defined here */

I expected the output:

hello world
3

Instead, the addition of the second write_fmt or the addition of an unwrap() crashes the program after compilation and upload but before the program executes. After a bit of testing, I found that adding lto = true under [profile.dev] in Cargo.toml fixes the issue.

Something that might be helpful is that when unwrap() is added to write_str, instead of printing the given string, it prints a piece of what seems like a compiler error, which matches the length of the supplied string, shown here: args.len()C:\Users\User\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\fmt\mod.rsunsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed 'isize::MAX'called 'Option::unwrap()' on a 'None' valueC:\Users\User\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\char\convert.rsC:\Users\User\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\str\iter.rsC:\Users\User\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\str\validations.rsErrorattempt to add with overflowC:\Users\User\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\iter\traits\accum.rsunsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed 'isize::MAX'attempt to add with overflowunsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed 'isize::MAX'C:\Users\User\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rust

Meta

rustc --version --verbose:

rustc 1.68.0-nightly (388538fc9 2023-01-05)
binary: rustc
commit-hash: 388538fc963e07a94e3fc3ac8948627fd2d28d29
commit-date: 2023-01-05
host: x86_64-pc-windows-msvc
release: 1.68.0-nightly
LLVM version: 15.0.6

Custom target avr-atmega328p.json:

{
    "arch": "avr",
    "cpu": "atmega328p",
    "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
    "max-atomic-width": 0,
    "env": "",
    "executables": true,
    "linker": "avr-gcc",
    "linker-flavor": "gcc",
    "linker-is-gnu": true,
    "llvm-target": "avr-unknown-unknown",
    "os": "unknown",
    "position-independent-executables": false,
    "exe-suffix": ".elf",
    "eh-frame-header": false,
    "pre-link-args": {
      "gcc": ["-mmcu=atmega328p"]
    },
    "late-link-args": {
      "gcc": ["-lgcc", "-lc"]
    },
    "target-c-int-width": "16",
    "target-endian": "little",
    "target-pointer-width": "16",
    "vendor": "unknown"
}
commonkestrel commented 1 year ago

@rustbot label +O-AVR

Patryk27 commented 11 months ago

Using LTO doesn't seem to be required anymore (i.e. it works in all cases) - checked using:

#![no_std]
#![no_main]

use core::fmt;
use core::fmt::Write;
use panic_halt as _;
use ufmt::uWrite;
use void::Void;

struct Serial<'a>(&'a mut dyn uWrite<Error = Void>);

impl fmt::Write for Serial<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let tx = core::hint::black_box(&mut self.0);

        for c in s.chars() {
            _ = tx.write_char(c);
        }

        Ok(())
    }
}

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);
    let mut serial = arduino_hal::default_serial!(dp, pins, 57600);
    let mut serial = Serial(&mut serial);

    _ = serial.write_fmt(format_args!("hello world\n"));
    _ = serial.write_fmt(format_args!("{}\n", 3));

    loop {
        //
    }
}

... and simavr.