Compiler panic while formatting code with inline assembly
Closed this issue · 2 comments
Command:
cargo +nightly fmt
Code:
use std::arch::asm;
use proptest::prelude::*;
fn main() {}
/// Вычислить d как результат (a * b / c), где при переполнении числа или при
/// делении на 0 результатом будет None.
fn calc_d(a: i16, b: i16, c: u8) -> Option<i16> {
// Референсная реализация для использовании в тестировании свойств
// (prop(-erty)test)
a.checked_mul(b).and_then(|it| it.checked_div(c as i16))
}
/// Вычислить d как результат (a * b / c), где при переполнении числа или
/// при делении на 0 результатом будет None.
fn calc_d_asm(mut a: i16, b: i16, c: u8) -> Option<i16> {
/// Флаги, указывающие на результат выполнения ассемблерной вставки
///
/// Поскольку ABI Rust'а нестабильно, мы не можем напрямую из ассемблера
/// мутировать переменную типа `Option<i16>`. Этот тип будет создаваться на
/// основании этого флага.
// тип перечисления будет занимать 1 беззнаковый байт (u8)
#[repr(u8)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
// мы не конструируем флаги напрямую, поэтому отключаем предупреждение
#[allow(dead_code)]
enum Flag {
Ok = 0,
Overflow = 1,
DivByZero = 2,
}
// По умолчанию считаем, что вставка выполнится успешно.
// Ассемблерная вставка будет менять этот флаг в случае ошибки.
let mut flag = Flag::Ok as u8;
unsafe {
#[rustfmt::skip]
asm!(
// умножаем `a` (в регистре `ax`) на `b`,
// записываем результат в `ax` (где и хранится `a`)
"imul ax, {b:x}",
// если произошло переполнение, то прыгаем на метку 2
// здесь и далее `f` - это `forward`, т. е. метка дальше в коде
// без такой пометки значение интерпретируется как число,
// что некорректно для операции `jo`
"jo 2f",
// проверяем делитель на 0
"cmp {c}, 0",
// если делитель равен 0, то прыгаем на метку 3
"je 3f",
// Производим само деление:
// Поскольку нам нужен `i16` как результат, одной операцией
// `idiv` воспользоваться не получиться.
// Следует расширить `ax` до 32 бит (регистр `dx:ax`),
// а `c` - до 16 бит, преобразовав
// беззнаковый байт `c` в положительное знаковое слово.
// расширяем знаком `ax` до 32 бит `dx:ax`
"cwd",
// расширяем `c` до 16 бит, преобразовав его в `i16`,
// выполняем деление `dx:ax` на `c`
"idiv {c:x}",
// прыгаем на конец вставки
"jmp 4f",
"2:",
// Произошло переполнение: устанавливаем наш флаг,
// чтобы вернуть None из функции, прыгаем на конец вставки
"mov {flag}, 1",
"jmp 4f",
"3:",
// Произошла попытка деления на 0: устанавливаем наш флаг,
// чтобы вернуть None из функции
"mov {flag}, 2",
// Конец вставки
"4:",
// регистр `ax` с доступом на чтение и запись,
// в который записывается значение переменной `a`
inout("ax") a,
// параметр под именем `b` с доступом на чтение
b = in(reg) b,
// параметр под именем `c` с доступом на чтение.
// Поскольку компилятор Rust требует для значений класса `reg`
// размерность не менее 16 бит, то мы преобразуем `c` в `u16` (noop).
c = in(reg) c as u16,
// использованный для временного хранения значения `a`, расширенного до 32 бит,
// регистр `dx`.
out("dx") _,
// параметр под именем `flag` с доступом на чтение и запись,
// в регистр которого записывается значение переменной `flag`,
// а размерность регистра равна 1 байту
flag = inout(reg_byte) flag,
// опции и свойства ассемблерной вставки:
// pure - вставка не имеет побочных эффектов
// (её результат зависит только от входных параметров)
// nomem - вставка не обращается к памяти
// nostack - вставка не использует стек
//
// данные опции и свойства являются указанием для компилятора,
// позволяющим ему оптимизировать код
options(pure, nomem, nostack),
);
}
// Реинтерпретируем значение флага (u8) как перечисление `Flag` (тоже u8)
// Поскольку мы не записываем в `flag` ничего, кроме значений 0, 1 или 2,
// то эта операция безопасна.
let flag: Flag = unsafe { std::mem::transmute(flag) };
// В зависимости от значения флага, возвращаем тот или иной результат
match flag {
// Если флаг равен `Flag::Ok`, то возвращаем результат вычисления, обёрнутый в `Some`
Flag::Ok => Some(a),
// Если флаг равен `Flag::Overflow` или `Flag::DivByZero`, то возвращаем `None`
Flag::Overflow | Flag::DivByZero => None,
}
}
/// Вычислить e как результат выражения (a - b) * (b - c), где при переполнении
/// числа результатом будет None.
fn calc_e(a: i16, b: i16, c: u8) -> Option<i16> {
// Референсная реализация для использовании в тестировании свойств
// (prop(-erty)test)
let lhs = a.checked_sub(b)?;
let rhs = b.checked_sub(c as i16)?;
lhs.checked_mul(rhs)
}
fn calc_e_asm(_a: i16, _b: i16, _c: u8) -> Option<i16> {
enum Flag {
Ok = 0,
OverflowLhs = 1,
OverflowRhs = 2,
OverflowMul = 3,
}
todo!()
}
// Тестирование свойств (property testing)
proptest! {
#[test]
// Для любых a, b, c из множеств i16, i16, u8 соответственно,
// выполнить функцию `calc_d` (референсная реализация) и `calc_d_asm` (наша реализация) и
// проверить, что их результаты равны.
//
// Если тест проходит, то `calc_d` и `calc_d_asm` эквивалентны, а так как `calc_d` работает
// корректно, то и `calc_d_asm` работает корректно.
fn test_d(a in any::<i16>(), b in any::<i16>(), c in any::<u8>()) {
let d = calc_d(a, b, c);
let d_asm = calc_d_asm(a, b, c);
assert_eq!(d, d_asm);
}
// Для любых a, b, c из множеств i16, i16, u8 соответственно,
// выполнить функцию `calc_e` (референсная реализация) и `calc_e_asm` (наша реализация) и
// проверить, что их результаты равны.
//
// Если тест проходит, то `calc_e` и `calc_e_asm` эквивалентны, а так как `calc_e` работает
// корректно, то и `calc_e_asm` работает корректно.
// #[test]
// fn test_e(a in any::<i16>(), b in any::<i16>(), c in any::<u8>()) {
// let e = calc_e(a, b, c);
// let e_asm = calc_e_asm(a, b, c);
// assert_eq!(e, e_asm);
// }
}
Output:
thread 'main' panicked at /rust/deps/annotate-snippets-0.9.2/src/display_list/from_snippet.rs:275:9:
SourceAnnotation range `(85, 87)` is bigger than source length `57`
stack backtrace:
0: 0x7faf4d294ffa - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h214716e6e0c5cd2c
1: 0x7faf4da0468a - core::fmt::write::hbd48ce2ad7284a0a
2: 0x7faf4ee08651 - std::io::Write::write_fmt::hb2eafdc8e5760cec
3: 0x7faf4d294e52 - std::sys::backtrace::BacktraceLock::print::hbc5009a8dd7de74f
4: 0x7faf4d297356 - std::panicking::default_hook::{{closure}}::he4a5a0eb6c634694
5: 0x7faf4d2971a0 - std::panicking::default_hook::h6ba3c19e5efafdd4
6: 0x7faf4c31eeb1 - std[d5e65f54d52c6b80]::panicking::update_hook::<alloc[543edef93a4acc51]::boxed::Box<rustc_driver_impl[5dcda6566a109f30]::install_ice_hook::{closure#0}>>::{closure#0}
7: 0x7faf4d297a68 - std::panicking::rust_panic_with_hook::h773803f4ebfbed1e
8: 0x7faf4d29783a - std::panicking::begin_panic_handler::{{closure}}::h09c6376b3729e5b0
9: 0x7faf4d2954a9 - std::sys::backtrace::__rust_end_short_backtrace::h1dd30efd00c5bb69
10: 0x7faf4d2974fc - rust_begin_unwind
11: 0x7faf49ce49c0 - core::panicking::panic_fmt::hcd050e92ce3ec7a3
12: 0x55f7e0dfb65d - <annotate_snippets[9b90caa08906aceb]::display_list::structs::DisplayList as core[47afb5b034596fb9]::convert::From<annotate_snippets[9b90caa08906aceb]::snippet::Snippet>>::from
13: 0x55f7e0c1edf4 - <rustfmt_nightly[b45938c28a441e9d]::format_report_formatter::FormatReportFormatter as core[47afb5b034596fb9]::fmt::Display>::fmt
14: 0x7faf4da0468a - core::fmt::write::hbd48ce2ad7284a0a
15: 0x7faf4d289b3e - <&std::io::stdio::Stderr as std::io::Write>::write_fmt::h141d1231c6b074d3
16: 0x7faf4d28a3f8 - std::io::stdio::_eprint::h337e8ced72dc4635
17: 0x55f7e0b08811 - rustfmt[deac11de4eeee1a2]::format_and_emit_report::<std[d5e65f54d52c6b80]::io::stdio::Stdout>
18: 0x55f7e0b0622f - rustfmt[deac11de4eeee1a2]::execute
19: 0x55f7e0b0211c - rustfmt[deac11de4eeee1a2]::main
20: 0x55f7e0af2a43 - std[d5e65f54d52c6b80]::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
21: 0x55f7e0af4ad9 - std[d5e65f54d52c6b80]::rt::lang_start::<()>::{closure#0}
22: 0x7faf4e9511c1 - std::rt::lang_start_internal::h5758e332aa2f8cc6
23: 0x55f7e0b09868 - main
24: 0x7faf48917e08 - <unknown>
25: 0x7faf48917ecc - __libc_start_main
26: 0x55f7e0ade1b9 - <unknown>
27: 0x0 - <unknown>
error: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rustfmt/issues/new?labels=bug
note: please make sure that you have updated to the latest nightly
note: please attach the file at `/home/bezkonca/uni/sem-3/comptech/lab3/lab3-rs/rustc-ice-2024-11-18T10_38_07-2250081.txt` to your bug report
query stack during panic:
end of query stack
File /home/bezkonca/uni/sem-3/comptech/lab3/lab3-rs/rustc-ice-2024-11-18T10_32_31-2249440.txt
:
thread 'main' panicked at /rust/deps/annotate-snippets-0.9.2/src/display_list/from_snippet.rs:275:9:
SourceAnnotation range `(85, 87)` is bigger than source length `57`
stack backtrace:
0: 0x7fca35c26665 - std::backtrace::Backtrace::create::h0dfab4e59fc1562a
1: 0x7fca34080175 - std::backtrace::Backtrace::force_capture::hce43afc2210a30d1
2: 0x7fca3311f4f5 - std[d5e65f54d52c6b80]::panicking::update_hook::<alloc[543edef93a4acc51]::boxed::Box<rustc_driver_impl[5dcda6566a109f30]::install_ice_hook::{closure#0}>>::{closure#0}
3: 0x7fca34097a68 - std::panicking::rust_panic_with_hook::h773803f4ebfbed1e
4: 0x7fca3409783a - std::panicking::begin_panic_handler::{{closure}}::h09c6376b3729e5b0
5: 0x7fca340954a9 - std::sys::backtrace::__rust_end_short_backtrace::h1dd30efd00c5bb69
6: 0x7fca340974fc - rust_begin_unwind
7: 0x7fca30ae49c0 - core::panicking::panic_fmt::hcd050e92ce3ec7a3
8: 0x558443dab65d - <annotate_snippets[9b90caa08906aceb]::display_list::structs::DisplayList as core[47afb5b034596fb9]::convert::From<annotate_snippets[9b90caa08906aceb]::snippet::Snippet>>::from
9: 0x558443bcedf4 - <rustfmt_nightly[b45938c28a441e9d]::format_report_formatter::FormatReportFormatter as core[47afb5b034596fb9]::fmt::Display>::fmt
10: 0x7fca3480468a - core::fmt::write::hbd48ce2ad7284a0a
11: 0x7fca34089b3e - <&std::io::stdio::Stderr as std::io::Write>::write_fmt::h141d1231c6b074d3
12: 0x7fca3408a3f8 - std::io::stdio::_eprint::h337e8ced72dc4635
13: 0x558443ab8811 - rustfmt[deac11de4eeee1a2]::format_and_emit_report::<std[d5e65f54d52c6b80]::io::stdio::Stdout>
14: 0x558443ab72ff - rustfmt[deac11de4eeee1a2]::execute
15: 0x558443ab211c - rustfmt[deac11de4eeee1a2]::main
16: 0x558443aa2a43 - std[d5e65f54d52c6b80]::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
17: 0x558443aa4ad9 - std[d5e65f54d52c6b80]::rt::lang_start::<()>::{closure#0}
18: 0x7fca357511c1 - std::rt::lang_start_internal::h5758e332aa2f8cc6
19: 0x558443ab9868 - main
20: 0x7fca2f745e08 - <unknown>
21: 0x7fca2f745ecc - __libc_start_main
22: 0x558443a8e1b9 - <unknown>
23: 0x0 - <unknown>
rustc version: 1.84.0-nightly (5ec7d6eee 2024-11-17)
platform: x86_64-unknown-linux-gnu
Comments:
It looks like #6392.
@sicikh thanks for the report. Any chance you can get the reproducible example down to a smaller size? rustfmt can work on code that doesn't compile so it should be easy to remove lines of code that don't contribute to the problem.
I looked into this more and it turns out the panic has nothing to do with the inline assembly. The issue is the use of #[rustfmt::skip]
on an expression that internally has trailing whitespace at the end of comment lines that use multi-byte unicode characters. rustfmt panics in the annotate-snippets dependency when it tries to report the trailing whitespace. This is a duplicate of #6409 and other linked issues.