Rust: Masalah memori tidak aman di Rust aman

Dibuat pada 17 Feb 2020  ·  38Komentar  ·  Sumber: rust-lang/rust

Saya memiliki program kecil (penyederhanaan fungsi pengujian dari proyek yang lebih besar) yang memotong array kecil dan mencoba mengakses elemen di luar batas dari potongan tersebut. Menjalankannya dengan cargo run --release menggunakan rilis stabil 1.41.0 mencetak sesuatu seperti ini (diuji pada macOS 10.15 dan Ubuntu 19.10):

0 0 3 18446744073709551615
[1]    21065 segmentation fault  cargo run --release

Sepertinya potongan yang dihasilkan memiliki panjang 2**64 - 1 , jadi pemeriksaan batas dihilangkan, yang dapat diprediksi menghasilkan segfault. Pada 1.39.0 dan 1.40.0 program yang sama mencetak apa yang saya harapkan:

0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', src/main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Masalahnya hilang jika saya melakukan salah satu dari yang berikut:

  • hapus salah satu dari dua panggilan do_test(...); di main() ;
  • hapus pengulangan for _ in 0..1 { ;
  • ganti loop for y in 0..x { dengan for y in 0..1 { ;
  • hapus baris z.extend(std::iter::repeat(0).take(x)); atau ganti dengan z.extend(std::iter::repeat(0).take(1)); ;
  • ganti loop for arr_ref in arr { dengan let arr_ref = &arr[0]; ;
  • tentukan RUSTFLAGS="-C opt-level=2" ;
  • tentukan RUSTFLAGS="-C codegen-units=1" .

Tebakan terbaik saya adalah -C opt-level=3 memungkinkan pengoptimalan bermasalah di LLVM, yang mengakibatkan kesalahan kompilasi. Hal ini dikuatkan oleh fakta bahwa MIR ( --emit mir ) dan LLVM IR sebelum pengoptimalan ( --emit llvm-ir -C no-prepopulate-passes ) sama untuk -C opt-level=2 dan -C opt-level=3 .

Beberapa info tambahan yang mungkin bisa membantu:

  • Saya tidak dapat mereproduksi masalah di taman bermain Rust (mungkin karena menggunakan codegen-units = 1 );
  • Saya tidak dapat mereproduksi masalah pada Windows 10 dengan rilis 1.41.0 (tidak tahu apa yang membuatnya berbeda);
  • cargo-bisect-rustc mengatakan regresi pertama kali terjadi di 2019-12-12 nightly, khususnya dalam komit ini . Ini tampak mencurigakan bagi saya, mengingat bahwa 1.40.0 , yang tidak menunjukkan masalah, dirilis setelah tanggal ini.

Saya melampirkan program sebaris jika repo GitHub tidak berfungsi (jika Anda ingin mengkompilasinya tanpa Cargo, gunakan rustc -C opt-level=3 main.rs ):

fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}
A-LLVM C-bug I-unsound 💥 ICEBreaker-LLVM P-medium T-compiler regression-from-stable-to-stable

Komentar yang paling membantu

Pereproduksi LLVM IR: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Jalankan opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Jika bagian dalam tanda kurung adalah i64 -1 , pengoptimalan buggy terjadi. Jika tidak ... mungkin tidak, tapi sulit untuk memastikannya.

Tampaknya berasal dari LLVM yang salah menambahkan nuw ke add nuw i64 %x, -1 sebagai bagian dari penyederhanaan Variabel Induksi. x adalah argumen untuk fungsi tersebut, dan nuw berarti tidak ada bungkus unsigned, jadi ini secara efektif menegaskan bahwa argumennya adalah 0, pada titik dalam fungsi yang tidak dijamin akan ada.

Membagi dua (sunting: dari LLVM 9 ke LLVM 10, yang @tmiasko katakan tidak terpengaruh) menghasilkan komit ini:

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

Kelihatannya menjanjikan, karena r366419 (komit yang dikembalikan oleh komit di atas) disertakan dalam cabang LLVM 9.0 yang digunakan Rust.

Semua 38 komentar

cc @ rust-lang / compiler
@rbot ping icebreakers-llvm

Tim rilis sedang mempertimbangkan untuk membuat rilis poin untuk Rust 1.41 (kita membahasnya secara singkat dalam pertemuan minggu lalu), dan saya ingin ini dimasukkan di dalamnya jika kita bisa segera mendapatkan PR.

Hai pemecah ICE LLVM! Bug ini telah diidentifikasi sebagai yang bagus
"Kandidat pemecah ICE LLVM". Jika bermanfaat, berikut beberapa
[instruksi] untuk mengatasi bug semacam ini. Mungkin lihat?
Terima kasih! <3

cc @comex @DutchGhost @ hanna-kruppe @hdhoang @heyrutvik @ JOE1994 @jryans @mmilenko @nagisa @nikic @ Noah-Kennedy @SiavoshZarrasvand @spastorino @vertexclique @vgxbj

Menjalankannya dengan proses kargo - lepaskan menggunakan rilis stabil 1.41.0 mencetak sesuatu seperti ini (diuji pada macOS 10.15 dan Ubuntu 19.10):

Saya tidak bisa mereproduksi ini di taman bermain . Program ini bekerja dengan baik di 1.41.0 dalam mode rilis.

EDIT: Ah, Anda sudah mengatakan itu.
Juga programnya baik-baik saja di Miri, jadi ini sepertinya bukan UB tapi salah kompilasi.

Hanya untuk menambahkan titik data, saya dapat mereproduksi ini di Linux dengan nightly terbaru:

[andrew<strong i="6">@krusty</strong> rust-69225]$ rustc --version
rustc 1.43.0-nightly (5e7af4669 2020-02-16)

[andrew<strong i="7">@krusty</strong> rust-69225]$ cat main.rs
fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

[andrew<strong i="8">@krusty</strong> rust-69225]$ rustc -C opt-level=3 main.rs

[andrew<strong i="9">@krusty</strong> rust-69225]$ ./main
0 0 3 18446744073709551615
zsh: segmentation fault (core dumped)  ./main

Saya dapat mereproduksi di atas dengan output yang sama persis dengan Rust 1,41 stable. Karat 1,40 stabil tidak menunjukkan masalah:

$ ./main
0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Saya pikir ini semua konsisten dengan laporan @dfyz , kecuali ini setidaknya menegaskan bahwa ini bukan khusus macOS.

  • cargo-bisect-rustc mengatakan regresi pertama kali terjadi di 2019-12-12 nightly, khususnya dalam komit ini . Ini tampak mencurigakan bagi saya, mengingat bahwa 1.40.0 , yang tidak menunjukkan masalah, dirilis setelah tanggal ini.

Ini diharapkan. 1.40.0 dirilis pada 2019-12-19 berdasarkan cabang beta , yang bercabang dari master enam minggu sebelumnya (sekitar waktu rilis 1.39.0). Lihat https://doc.rust-lang.org/book/appendix-07-nightly-rust.html untuk lebih lanjut tentang saluran rilis.

Jika saya harus menebak, menurut saya https://github.com/rust-lang/rust/pull/67015 kemungkinan besar penyebabnya. Ini memperbaiki 3 masalah codegen, sehingga menyentuh kode codegen-kritis.
Cc @ osa1 @ oli-obk @wesleywiser

Terima kasih atas pingnya. Saat ini saya sedang membuat bangunan untuk diselidiki. Mungkin saja ini adalah bug yang diperkenalkan dengan # 67015. Kemungkinan lainnya adalah # 67015 baru saja mengungkap bug yang ada.

Saya berhasil mereproduksi segfault menggunakan rustc nightly di Linux, tetapi tidak dengan build saya yang dihasilkan dengan config.toml ini:

config.toml

[llvm]

[build]

[install]

[rust]
optimize = true
debug = true
codegen-units = 0
debug-assertions = true
debuginfo-level = 2

[target.x86_64-unknown-linux-gnu]
llvm-config = "/usr/bin/llvm-config-9"

[dist]

Menggunakan rustc nightly saya memeriksa MIR sebelum dan sesudah ConstProp dan MIR identik. Jadi jika ini disebabkan oleh ConstProp maka itu karena perbedaan kode yang dihasilkan dari perpustakaan, bukan program ini.

Regresi di 033662dfbca088937b9cdfd3d9584015b5e375b2

@rustbot memodifikasi label: -E-needs-bisection


@ osa1 yang debug-assertions = true mungkin harus disalahkan. Ketika saya mencoba untuk mengkompilasi (dengan kompiler vanilla nightly) program dengan -C debug-assertions=y program panik daripada segfault

Saya pikir saya menyelesaikannya! Mengembalikan a983e0590a43ed8b0f60417828efd4e79b51f494 memperbaiki masalah. Sepertinya sepanjang hari itu penyebabnya, tapi saya tidak bisa mengujinya di tempat kerja :) Adakah yang bisa membantu saya untuk membuat PR terbaik untuk masalah ini? Saya pikir cara terbaik adalah menambahkan testcase yang harus gagal, tetapi tampaknya ini sangat spesifik untuk platform dll, jadi mungkin itu bukan ide yang bagus? Ada ide di sini? Terima kasih!

(Ini sudah dibelah dua oleh OP)

Saya berhasil mereproduksi ini dengan bangunan lokal dengan debug-assertions dimatikan (terima kasih untuk itu @ hellow554).

Beberapa PR dalam rollup menyebabkan konflik saat dikembalikan karena sejak saat itu kami memiliki rustfmt -ed semuanya, tetapi saya yakin masalah ini karena # 67174.

edit: sepertinya kami menemukan ini pada saat yang sama @shahn :)

@lqd ya, ini adalah masalah yang menyertakan komit yang saya rujuk di atas. Itu pelakunya.

Untuk menambahkan titik data lain, masalah hilang saat codegen-units disetel ke 3 atau kurang (anggap profil rilis, dengan incremental=false ). Dengan kata lain saya dapat mereproduksi ketika codegen-units adalah 4 atau lebih besar.

Panggilan ke panic_bounds_check menghilang setelah LLVM Jump Threading lulus. Saya dapat mereproduksi masalah dengan memilih dari LLVM 9, tetapi tidak dari LLVM 10.

Jadi, saya memeriksa dan membangun panggung 1 rustc ( ./x.py build -i --stage 1 ), membangun kembali libstd ( ./x.py build -i --stage 1 --keep-stage 0 src/libstd ) tanpa # 67174, dan mengkompilasi ulang program segfault dengan empat unit codegen ( rustc +stage1 -C opt-level=3 -C codegen-units=4 main.rs ). Seperti yang diharapkan, ini membuat segfault itu pergi. Jika saya menerapkan # 67174 kembali, segfault akan kembali.

Ini berarti saya sekarang memiliki dua kompiler yang hanya berbeda di pustaka standar yang mereka gunakan. Sebut saja compiler ini GOOD (tidak ada segfault) dan BAD (segfault).

Saya kemudian memperhatikan bahwa 4 file _unoptimized_ *.ll dihasilkan oleh GOOD ( -C no-prepopulate-passes ) hampir sama dengan yang dihasilkan oleh BAD (satu-satunya perbedaan Saya melihat ada ID acak yang berbeda dalam nama fungsi), tetapi file _optimized_ *.ll (tidak ada -C no-prepopulate-passes ) sangat berbeda. Saya bukan ahli kompiler dengan cara apa pun, tetapi karena program yang sedang dikompilasi persis sama dalam kedua kasus, kedua kompiler yang tepat persis sama, dan satu-satunya perbedaan adalah di pustaka standar yang telah dikompilasi, saya _think_ LTO mungkin terlibat .

Memang, jika saya lulus salah satu dari -Z thinlto=no , -C lto=no , -C lto=yes , -C lto=thin (pada titik ini saya benar-benar tidak yakin, tapi saya rasa semua bentuk dari -C lto berbeda dari ThinLTO, yang digunakan secara default) menjadi BAD , segfault sekali lagi menghilang.

Apakah tampaknya KPP mungkin salah di sini?

Saya telah membaca kasus uji. Saya telah membaca komit yang dikembalikan. Aku masih belum mengerti apa yang terjadi. Apa yang rusak?

Apa yang rusak?

Saya tidak percaya pada titik ini siapa pun dapat mengatakan dengan pasti apa yang benar-benar rusak, tetapi analisis tentatif saya adalah ini (harap ambil yang berikut ini dengan sebutir garam, saya tidak berada di tim Rust atau tim LLVM, semua saya yang bisa dilakukan adalah mengotak-atik kompiler dan menatap LLVM IR):

  • kami menghapus pemeriksaan overflow dari satu baris di Layout::repeat() dari pustaka standar, yang akhirnya menyebabkan ketidakamanan memori ini. Secara matematis, menggunakan penambahan yang tidak dicentang di sini seharusnya sangat aman - komentar dalam fungsi ini (dan juga di Layout::pad_to_align() ) menjelaskan alasannya;
  • contoh kode saya yang mendemonstrasikan masalah ini bahkan tidak memanggil fungsi ini, tetapi secara eksplisit menggunakan Vec , yang secara implisit menggunakan Vec::reserve_internal() , yang pada gilirannya memanggil Layout::repeat() ;
  • Layout::repeat() ditandai sebagai #[inline] , dan tampaknya satu-satunya perbedaan yang relevan antara GOOD dan BAD adalah apakah fungsi ini dimasukkan ke dalam do_test() atau tidak. Misalnya, memulihkan pemeriksaan overflow melarang inline dan memperbaiki masalah; menghapus atribut #[inline] menyebabkan efek yang sama; menonaktifkan LTO menonaktifkan inline untuk fungsi perpustakaan dan sekali lagi memperbaiki masalah.

Jika ini benar (sekali lagi, saya tidak 100% yakin tentang hal-hal di atas), ini berarti beberapa LLVM pass nakal atau kombinasi operan salah mengoptimalkan IR setelah inline. Itulah yang saat ini saya coba selidiki, tetapi sayangnya itu tidak mudah (setidaknya bagi pemecah ICE non-LLVM seperti saya) karena perbedaan IR antara GOOD dan BAD cukup besar. Sepertinya versi buruk menghilangkan panic_bounds_check , tetapi saya masih tidak yakin mengapa.

Juga, terinspirasi oleh komentar @tmiasko , saya mencoba mengkompilasi rustc dengan versi LLVM yang berbeda, dan sepertinya LLVM 10rc1 memperbaiki masalah (versi LLVM terbaru yang saya coba adalah 9.0.1). Kelihatannya bukan pass Jump Threading yang harus disalahkan, karena saya tidak melihat komit yang relevan antara 9.0.1 dan 10rc1.

Jika mengembalikan perubahan Layout::repeat() hanya menyembunyikan gejala dari satu kemunculan

Jika mengembalikan perubahan Layout :: repeat () hanya menyembunyikan gejala dari satu kejadian spesifik dari bug yang tidak terkait, apakah mengembalikan benar-benar hal yang benar untuk dilakukan?

Saya rasa tidak masalah jika:

  • Perubahan dikirim
  • Itu membuat bug lebih mudah dipicu, memengaruhi banyak pengguna
  • Memperbaiki dengan benar akan memakan waktu lama

Jika ini ditahan maka saya pikir saya akan mengembalikan perubahan, mengirimkan rilis kecil untuk membebaskan pengguna (semuanya bekerja dengan baik tanpa perubahan meskipun bug masih ada), dan kemudian fokus pada bug yang sebenarnya.

Di kompiler lain saya ingat benar-benar melakukan ini. Kami mengembalikan perubahan di cabang rilis, tetapi tidak di master (yang bukan praktik yang baik, ini menyebabkan masalah nanti), mengirimkan rilis minor baru. Kemudian perbaiki bug yang sebenarnya.

Bagaimanapun, selama bug akan diprioritaskan dan diperbaiki, dan komit yang membuat bug lebih mudah dipicu bukanlah perbaikan bug itu sendiri, saya tidak melihat ada masalah dengan mengembalikannya untuk saat ini.

Jadi pertanyaannya adalah, apakah bug ini sangat mudah dipicu? Sejauh ini kami memiliki satu laporan dengan kasus uji yang agak terlibat di mana mencoba meminimalkan lebih jauh (seperti membuka gulungan for _ in 0..1 tampaknya sepele) gagal mereproduksi.

Menurut saya bug itu tidak mudah dipicu, sepertinya saya baru saja tidak beruntung.

Bagaimanapun, saya sangat menghargai masalah yang dialami @shahn untuk mengembalikan perubahan Layout::new() , tetapi IMO mengembalikannya _bukan_ hal yang benar untuk dilakukan dalam kasus ini. Alasan saya (selain apa yang dikatakan @SimonSapin ):

  • menghapus pemeriksaan overflow di Layout::repeat() memungkinkan LLVM untuk menyebariskan Vec::reserve() dalam build rilis. Ini mungkin memberikan peningkatan kinerja yang bagus dalam beberapa kasus (meskipun ini harus diukur, tentu saja);
  • secara harfiah fungsi sebelumnya di libcore/alloc.rs ( Layout::pad_to_align() ) menggunakan pola yang sama dari penambahan yang tidak dicentang dengan komentar yang persis sama menjelaskan apa yang memungkinkannya. Mengembalikan cek overflow di Layout::repeat() tetapi tidak di Layout::pad_to_align() tampaknya sangat aneh bagi saya;
  • jika ada yang benar-benar diblokir dalam masalah ini (saya pasti tidak), ada banyak solusi lain yang tidak melibatkan perubahan stdlib (misalnya, nonaktifkan ThinLTO, ubah level pengoptimalan, kurangi jumlah unit codegen).

Mungkin secara lokal melemparkan pernyataan defensif yang termasuk rilis dari invarian sebagai prasyarat sehingga panik dengan detail spesifik untuk memburu kasus tepi tertentu ini atau beberapa debugger-fu? Saya berani bertaruh itu adalah perhitungan yang tidak diperiksa dalam kondisi tertentu.

Kemudian, ketika dilacak (di suatu tempat di LLVM seperti yang baru saya pelajari, ty @dyfz), kasus uji regresi akan luar biasa sehingga tidak terjadi lagi. 🙏

Pereproduksi LLVM IR: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Jalankan opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Jika bagian dalam tanda kurung adalah i64 -1 , pengoptimalan buggy terjadi. Jika tidak ... mungkin tidak, tapi sulit untuk memastikannya.

Tampaknya berasal dari LLVM yang salah menambahkan nuw ke add nuw i64 %x, -1 sebagai bagian dari penyederhanaan Variabel Induksi. x adalah argumen untuk fungsi tersebut, dan nuw berarti tidak ada bungkus unsigned, jadi ini secara efektif menegaskan bahwa argumennya adalah 0, pada titik dalam fungsi yang tidak dijamin akan ada.

Membagi dua (sunting: dari LLVM 9 ke LLVM 10, yang @tmiasko katakan tidak terpengaruh) menghasilkan komit ini:

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

Kelihatannya menjanjikan, karena r366419 (komit yang dikembalikan oleh komit di atas) disertakan dalam cabang LLVM 9.0 yang digunakan Rust.

Triase T-compiler: P-medium, berdasarkan ringkasan situasi berikut:

pnkfelix: Sepertinya item pekerjaan yang tersisa untuk # 69225 adalah 1. perbaiki LLVM (baik dengan memilih 58e8c793d0e43150a6452e971a32d7407a8a7401 atau dengan meningkatkan ke LLVM 10) lalu 2. readd PR # 67174.
pnkfelix: namun tidak satu pun dari ini yang menurut saya merupakan item prioritas tinggi.
pnkfelix: Setidaknya, bug LLVM ini tampaknya tidak lebih baik atau lebih buruk daripada bug codegen LLVM lainnya. Yang saya kira itulah yang baru saja dikatakan

Pembaruan: peningkatan ke LLVM 10 sedang dilakukan dalam PR # 67759

Pembaruan 2: Mungkin tidak bijaksana untuk membabi buta memilih ceri komit pengembalian mereka, karena kami mungkin memilih ceri yang asli untuk beberapa alasan, dan dengan demikian pengembalian dapat memiliki efek hilir yang tidak diinginkan. Paling tidak, kita tidak boleh mencobanya tanpa memahami konsekuensinya (dan, mengingat upaya untuk meningkatkan ke LLVM 10, kita mungkin tidak boleh mencoba mengambil ceri sama sekali, karena ini akan menjadi usaha yang sia-sia ...)

Apakah komit asli yang dipilih? Setidaknya dari komentar @comex 'yang tidak jelas bagi saya ("termasuk dalam cabang LLVM 9.0 yang digunakan Rust" bisa juga berarti itu hanya bagian dari LLVM 9.0).

Komit yang dimaksud adalah sangat lokal dan kecil perubahan yang menambahkan satu parameter untuk panggilan fungsi dan secara harfiah mengatakan it is safe [in this case] to add SCEV::FlagNSW (dan menilai dari kode, parameter baru juga dapat SCEV::FlagNUW ), jadi saya pikir sangat mungkin inilah yang menyebabkan kesalahan pengoptimalan. Saya dapat mengkonfirmasi bahwa menghapus parameter ini (yaitu, mengubah (void)getAddRecExpr(getAddExpr(StartVal, Accum, Flags), Accum, L, Flags); menjadi (void)getAddRecExpr(getAddExpr(StartVal, Accum), Accum, L, Flags); ) memperbaiki masalah.

Selain itu, komit bermasalah ini _not_ dipilih. Ini hanya nasib buruk - sepertinya pengembalian terjadi setelah 9.0.0 dibuat, jadi upstream 9.0.0 masih memiliki parameter yang mengganggu. Kembalinya juga tidak di- backport ke 9.0.1, untuk beberapa alasan. 10.0.0-rc1 dan versi yang lebih baru memiliki pengembalian.

Berikut adalah komentar yang menjelaskan mengapa sebenarnya tidak aman menambahkan nsw atau nuw sini. Mungkin ide yang bagus untuk berbicara dengan pengembang LLVM tentang ini, tetapi saya pikir memilih ceri untuk mengembalikan akan memperbaiki masalah ini dan tidak akan memiliki efek yang tidak diinginkan sama sekali, karena sangat kecil dan mandiri.

PS Pujian besar untuk

FWIW Saya dapat mengonfirmasi bahwa https://github.com/llvm/llvm-project/commit/58e8c793d0e43150a6452e971a32d7407a8a7401 aman untuk dipilih, ini adalah perubahan konservatif. Lihat juga https://lists.llvm.org/pipermail/llvm-dev/2019-September/135195.html jika Anda tertarik pada konteks lebih lanjut mengenai apa masalah dengan bendera nowrap SCEV.

Saya rasa saya baru saja menemukan cara untuk mereproduksi masalah tersebut bahkan setelah mengembalikan # 67174. Ini adalah program yang sedikit lebih lama, tetapi masih aman yang dapat diandalkan segfaults di Windows, Linux dan macOS menggunakan nightly terbaru dengan # 67174 dikembalikan:

fn do_test(x: usize) {
    let mut arr = vec![vec![0u8; 3]];

    let mut z = vec![0];
    for arr_ref in arr.iter_mut() {
        for y in 0..x {
            for _ in 0..1 {
                z.reserve_exact(x);
                let iterator = std::iter::repeat(0).take(x);
                let mut cnt = 0;
                iterator.for_each(|_| {
                    z[0] = 0;
                    cnt += 1;
                });
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &mut arr_ref[a..b];
                slice[1 << 24] += 1;
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

Windows:

PS> rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
PS> rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target\release\rust-segfault.exe`
error: process didn't exit successfully: `target\release\rust-segfault.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

Linux:

$ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
$ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 1.13s
     Running `target/release/rust-segfault`
Segmentation fault (core dumped)

macOS:

λ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
λ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target/release/rust-segfault`
[1]    24331 segmentation fault  rustup run nightly cargo run --release

Program ini tidak tergantung pada jumlah unit codegen, sehingga segfault di Playground, terlalu (pada stabil, beta, dan malam). Saya juga mereproduksi ini dengan mengkompilasi rustc dari master (dengan # 67174 dikembalikan) terkait dengan LLVM 9.

Bug LLVM yang mendasarinya masih sama, jadi meningkatkan ke LLVM 10 atau memilih ceri perbaikan LLVM membuat segfault hilang.

Saya sangat berharap saya mengerti apa yang terjadi dengan lebih baik. Tampaknya pemeriksaan batas dihilangkan karena tambahan nuw , yang berasal dari nilai SCEV yang di-cache dengan tidak benar (seperti di program C dari utas @nikic yang ditautkan ke). Tetapi pada saat pengoptimalan yang buruk terjadi, saya hampir tidak dapat mengenali program sederhana saya melalui lapisan blok dasar LLVM; lebih buruk lagi, setiap perubahan yang tampaknya tidak ada operasi dalam kode sumber (misalnya, menghapus variabel cnt ) mengarah ke IR LLVM yang tampak sangat berbeda dan membuat masalah hilang.

Kesan saya adalah bahwa 1.41.1 baru saja diselesaikan di # 69359 (waktu yang buruk di pihak saya), jadi tidak banyak yang bisa dilakukan pada saat ini. Apakah setidaknya merupakan ide yang baik untuk memperbarui komentar di Layout::repeat() dengan penjelasan yang lebih rinci tentang masalah LLVM? Jika demikian, saya dapat mengirim PR.

Kesan saya adalah bahwa 1.41.1 baru saja diselesaikan di # 69359 (waktu yang buruk di pihak saya), jadi tidak banyak yang bisa dilakukan pada saat ini.

Jika tambalan yang kami sertakan di 1.41.1 tidak benar-benar memperbaiki masalah, kami harus mempertimbangkan kembali apakah kami ingin mem-backport perbaikan baru dan membangun kembali rilis. Ada konsensus di tim pertemuan rilis tidak backport memperbaiki LLVM, tapi saya pribadi berpikir lain PoC baru ini bisa menjamin diskusi lain pada topik.

cc @ Mark-Simulacrum @ rust-lang / rilis

@dfyz kami akan mencoba untuk mendapatkan build lain dari 1.41.1 dengan LLVM fix backported, sementara kami menunggu konsensus untuk benar-benar mengirimkannya.

FWIW, bagi saya reproduksi baru berfungsi seperti yang diharapkan ( index out of bounds ) pada stable 1.38.0 dan sebelumnya, tetapi segfaults pada 1.39.0 dan yang lebih baru. Tidak ada banyak perbedaan dalam LLVM antara 1.38 dan 1.39 (https://github.com/rust-lang/llvm-project/compare/71fe7ec06b85f612fc0e4eb4134c7a7d0f23fac5...8adf9bdccfefb8d03f0e8db3b012fb4 semacam perbedaan yang dihasilkan Rustda1580a) di sepanjang jalan juga.

alat reproduksi baru bekerja seperti yang diharapkan (indeks di luar batas) pada stabil 1.38.0

Saya (tidak sengaja) menemukan bahwa pengaturan -C codegen-units=1 pada 1.38.0 mereproduksi segfault tersebut. 1.37.0 tampaknya aman bagi saya (tidak ada kombinasi opsi yang saya coba menghasilkan segfault).

Abaikan itu, 1.37.0 menggunakan LLVM 8.
Anehnya, perbedaan IR LLVM antara 1.37.0 dan 1.38.0 (dengan -C codegen-units=1 ) hanyalah satu baris:

- %71 = icmp eq {}* %70, null
+ %71 = icmp ule {}* %70, null

(di mana %70 diturunkan dari hasil <core::slice::IterMut<T> as core::iter::traits::iterator::Iterator>::next() )

Ini saja sudah cukup untuk mengelabui LLVM agar menambahkan nuw menjadi add nuw i64 %x, -1 .

1.37.0 tampaknya aman bagi saya (tidak ada kombinasi opsi yang saya coba menghasilkan segfault).

Itu menggunakan LLVM 8, jadi perubahan SCEV yang disalahkan seharusnya tidak ada sama sekali.

Itu menggunakan LLVM 8

Saya buruk, maaf atas kebingungan (saya sangat senang untuk menguranginya menjadi diff satu baris, saya bahkan tidak repot-repot memeriksa versi LLVM).

Kami menyiapkan artefak 1.41.1 baru dengan perbaikan LLVM yang dipilih di dalamnya. Anda dapat mengujinya secara lokal dengan:

RUSTUP_DIST_SERVER=https://dev-static.rust-lang.org rustup update stable

ping di https://github.com/rust-lang/rust/issues/69225#issuecomment -586941455

[triagebot] Masalah berhasil diselesaikan tanpa keterlibatan apa pun dari tim kompiler yang di-ping.
Bretty bagus.

1.41.1 keluar, saya rasa sudah waktunya untuk menutup masalah ini.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat