Open rihardsk opened 1 year ago
I think this code: https://github.com/jamesmunns/postcard/blob/62c05473f76ead781aaa9d4a3e4057b86e543747/src/ser/serializer.rs#L321-L388 (and support for the collect_str
method) were added due to issues with chrono (see #32).
If I had to guess, I would suspect an AVR miscompilation of dyn Trait
items, which is used by the core::fmt::Write
implementation?
What do you mean by
dyn Trait
items, which is used by thecore::fmt::Write
implementation
I didn't notice any dyn Trait
items when looking around.
I tried to probe this thing some more, but the only thing I discovered, is that NaiveDateTime
s core::fmt::Display
implementation seems to be working ok on its own. So that makes it somewhat more likely that something in postcard
is causing this.
But I'm not sure how to proceed from here. Should I try to somehow inline collect_str
implementation and pepper it with ufmt::writeln!
calls to find out where exactly it breaks? Or could this be enough info as is, to open an issue on https://github.com/rust-lang/rust and see what they say about this?
As far as I am aware, there are quite a few parts under the hood of the fmt::Write
and write!()
machinery that may end up using dyn Trait
s.
An interesting test might be something like:
use core::fmt::Write;
let mut buf: heapless::String<256> = heapless::String::new();
let data: NaiveDateTime = ...; // your test data here
write!(&mut buf, "{}", &data).unwrap();
And see if that works. This would use the same core::fmt::Write
machinery I am using in postcard, without actually using postcard.
If that works on other targets (desktop, cortex-m), and NOT on AVR, I'd be pretty convinced it was something wrong with the AVR code generation. If that DOES work on AVR, then I'd certainly be confused!
When I said that
the only thing I discovered, is that NaiveDateTimes core::fmt::Display implementation seems to be working ok on its own
I meant that I already tried something similar. This is what i did:
struct SerialWriter<'a, T> {
serial: &'a mut T,
}
impl<'a, T> Write for SerialWriter<'a, T> where T: uWrite {
fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> {
ufmt::uwrite!(&mut self.serial, "{}", s).map_err(|_| core::fmt::Error::default())?;
Ok(())
}
}
let mut serial_writer = SerialWriter { serial: &mut serial };
let entry = NaiveDateTime::MIN;
write!(&mut serial_writer, "{}", entry).unwrap();
// This outputs:
// -262144-01-01 00:00:00
// over the serial connection
this works on AVR.
I tried out your code as well. It produces the same output when printed out with ufmt::uwriteln!(&mut serial, "{}", buf.as_str()).unwrap()
. So that probably means that there's something else going on.
Btw, I searched for "AVR miscompilation" in the Rust repo and found this https://github.com/rust-lang/rust/issues/74743 which does indicate that the AVR backend has issues with dyn Trait
stuff. But I guess the observations above indicate that this might not be relevant (unless postcard also uses some dyn Trait
stuff internally). I didn't find anything else that seems relevant, though.
ufmt
explicitly doesn't use dynamic dispatch: https://docs.rs/ufmt/latest/ufmt/
No dynamic dispatch in generated code
It might be worth trying without using ufmt
at all to see if you get the same issues. Since core::fmt::Write
/writeln!()
DOES use dyn dispatch, and ufmt::Write
/ufmt::writeln!()
DOESN'T, this is a pretty significant difference.
Here's how I invoked your code. It doesn't crash.
use core::fmt::Write;
let mut buf: heapless::String<256> = heapless::String::new();
let data: NaiveDateTime = NaiveDateTime::MIN; // your test data here
// This uses core::fmt::Write
write!(&mut buf, "{}", &data).unwrap();
// This uses ufmt::Write. Added for convenience to check the results
ufmt::uwriteln!(&mut serial, "{}", buf.as_str()).unwrap();
AFAIK I'm exercising core::fmt::Write
on the 5th line. The last line uses ufmt::Write
but it's only for convenience and doesn't affect what's happening before that, commenting it out has no effect other than me not seeing the value of buf
(still no crash without it). Is this what you're asking, or am I misunderstanding something?
btw, the newest nightly (since https://github.com/rust-lang/rust/pull/114048) brought lots of fixes for the AVR backend - it might be worthwhile to recheck on it 🙂
Thanks, I'll have a look as soon as I get a chance.
Whenever i try to serialize a
chrono::NaiveDateTime
or any struct that contains it internally, the program resets, if it's being run on an AVR microcontroller (ATmega328p specifically). It can be as simple as:Serializing other stuff seems to work fine, e.g., this example code from the docs works as expected:
Also, the same code runs without issue on x64, which makes me suspect a miscompilation on AVR but verifying that seems to be beyond my ability currently (tried looking at the AVR disassembly but there's simply too much going on there).
I'm building the code using the
nightly-2023-03-24
toolchain.Minimal reproducible example
main.rs
Cargo.toml
Other things to note
I'm using
postcard
rev62c05473f76ead781aaa9d4a3e4057b86e543747
with the following diff applied to it to work around https://github.com/jamesmunns/postcard/issues/82heapless
is at rev644653bf3b831c6bb4963be2de24804acf5e5001
.