rust-lang/rustfmt

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.