Closed mutantbob closed 2 years ago
Since I looked it up, here's the expected/actual in hex, which makes the bug more clear:
input:
[0xf800, 0x07e0, 0x001f]
expected:
debug buf [0x00f8, 0xe007, 0x1f00] mapped
actual:
debug buf [0x0000, 0xe0e0, 0x1f1f] mapped
Hi,
Could you please check with opt-level = 1
? It feels like a bug nonetheless, but I'm curious about the result 🙂
Also: @rustbot claim
I'm analyzing the codegen bug - simplest reproduction so far:
#![no_std]
#![no_main]
use panic_halt as _;
#[arduino_hal::entry]
fn main() -> ! {
let pixels = [0];
for pixel in pixels.map(|n| n + 1) { // works with `.into_iter().map()`
print(pixel);
}
loop {
//
}
}
#[inline(never)]
fn print(pixel: u16) {
let dp = unsafe { arduino_hal::Peripherals::steal() };
let pins = arduino_hal::pins!(dp);
let mut serial = arduino_hal::default_serial!(dp, pins, 57600);
let _ = ufmt::uwriteln!(serial, "{}", pixel);
}
Got it!
The simplest reproduction:
#![no_std]
#![no_main]
#![feature(bench_black_box)]
use core::hint::black_box;
use panic_halt as _;
#[arduino_hal::entry]
fn main() -> ! {
if let Some(value) = next() {
print(value);
}
loop {
//
}
}
#[inline(never)]
fn next() -> Option<u16> { // this corresponds to `array.into_iter().next()` from the original code
// (note that the author doesn't call those functions by hand, but that's
// how the loop gets desugared)
black_box(Some(12345))
}
#[inline(never)]
fn print(n: u16) {
let dp = unsafe { arduino_hal::Peripherals::steal() };
let pins = arduino_hal::pins!(dp);
let mut serial = arduino_hal::default_serial!(dp, pins, 57600);
let _ = ufmt::uwriteln!(serial, "{}", n);
}
This prints 14649
(0x3939
in hex) instead of 12345
(0x3039
in hex), because the generated binary accidentally overwrites the high-order byte with the low-order one:
After calling next()
(call 0xa6
on the screenshot), the returned number - 12345
(0x3039
in hex) - gets stored into r23:r24 (you can see that r23=0x39, r24=0x30, and AVR being little-endian means the number is reconstructed from r24 first, r23 second).
print()
expects its n: u16
parameter to be stored in r24:r25
, so right before calling print()
, the codegen has a simple job: move r23:r24
into r24:r25
; and that's where the bug comes into play.
What the code generator says is:
; (so r23:r24 is the input number; r24:r25 is where we want the number to be.)
mov r24, r23
mov r25, r24
... which always sets r24
and r25
to the same value (r23
) - what it should generate instead is:
mov r25, r24
mov r24, r23
Overall, that's an omission on the LLVM's side (when moving register pairs, it should consider whether the registers are overlapping or not) - I'll try to prepare a patch 🙂
I just updated to nightly-2022-07-08 and some weirdness I was seeing with ufmt
seems to have been fixed.
Under nightly-2022-05-10 I would see this in the serial log:
start FIFO length = 00e@@⸮
Under nightly-2022-07-08 I get
start FIFO length = 307208 ; %16=8
It also fixed the problems I was having with the st7789 crate that led me to file this bug.
I tried this code on AVR (arduino Uno = atmega328p) with opt-level="z":
I expected to see this happen:
debug buf [248, 57351, 7936] mapped
Instead, this happened:
debug buf [0, 57568, 7967] mapped
An alternate code that emits the correct output is
This compiler error is corrupting the pixel stream transmitted via SPI to an ST7789 display from an Arduino Uno. What appears to be happening is that instead of the bytes being swapped, the LSB is being copied to the MSB. I do not have the expertise to decompile and examine the assembly code.
Meta
rustc --version --verbose
:The malfunction also occurs with
cargo +nightly
.rustc +nightly --version --verbose
The full code can be cloned from https://github.com/mutantbob/rust-avr-code-generation-bug