Rust: Speicherunsicherheitsproblem in sicherem Rost

Erstellt am 17. Feb. 2020  ·  38Kommentare  ·  Quelle: rust-lang/rust

Ich habe ein kleines Programm (eine Vereinfachung einer Testfunktion aus einem größeren Projekt), das ein kleines Array schneidet und versucht, auf ein außerhalb der Grenzen liegendes Element des Slice zuzugreifen. Wenn Sie es mit cargo run --release Verwendung der stabilen Version 1.41.0 ausführen, wird so etwas gedruckt (getestet unter macOS 10.15 und Ubuntu 19.10):

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

Es sieht so aus, als hätte das resultierende Slice irgendwie die Länge 2**64 - 1 , daher wird die Überprüfung der Grenzen weggelassen, was vorhersehbar zu einem Segfault führt. Auf 1.39.0 und 1.40.0 druckt dasselbe Programm, was ich erwarten würde:

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.

Das Problem verschwindet, wenn ich eine der folgenden Aktionen ausführe:

  • Entfernen Sie einen der beiden do_test(...); -Anrufe in main() .
  • Entfernen Sie die for _ in 0..1 { -Schleife.
  • Ersetzen Sie die for y in 0..x { -Schleife durch for y in 0..1 { .
  • Entfernen Sie die Zeile z.extend(std::iter::repeat(0).take(x)); oder ersetzen Sie sie durch z.extend(std::iter::repeat(0).take(1)); .
  • Ersetzen Sie die for arr_ref in arr { -Schleife durch let arr_ref = &arr[0]; .
  • Geben Sie RUSTFLAGS="-C opt-level=2" .
  • Geben Sie RUSTFLAGS="-C codegen-units=1" .

Ich gehe davon aus, dass -C opt-level=3 einen problematischen Optimierungsdurchlauf in LLVM ermöglicht, der zu einer Fehlkompilierung führt. Dies wird durch die Tatsache bestätigt, dass MIR ( --emit mir ) und LLVM IR vor Optimierungen ( --emit llvm-ir -C no-prepopulate-passes ) für -C opt-level=2 und -C opt-level=3 .

Einige zusätzliche Informationen, die hilfreich sein könnten:

  • Ich kann das Problem auf dem Rust-Spielplatz nicht reproduzieren (vermutlich, weil codegen-units = 1 ).
  • Ich kann das Problem unter Windows 10 nicht mit derselben Version von 1.41.0 reproduzieren (keine Ahnung, was es anders macht).
  • cargo-bisect-rustc sagt, dass die Regression zum ersten Mal in der nächtlichen 2019-12-12 stattgefunden hat, speziell in diesem Commit . Dies erscheint mir verdächtig, da 1.40.0 , das das Problem nicht aufweist, nach diesem Datum veröffentlicht wurde.

Ich hänge das Programm inline an, falls das GitHub-Repo nicht funktioniert (wenn Sie es ohne Cargo kompilieren möchten, verwenden Sie 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

Hilfreichster Kommentar

LLVM-IR-Wiedergabegerät: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Führen Sie opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Wenn das Innere der Klammern i64 -1 , ist die fehlerhafte Optimierung aufgetreten. Wenn es nicht ... ist es vielleicht nicht, aber es ist schwer sicher zu sein.

Es scheint von LLVM zu stammen, das im Rahmen des Durchlaufs zur Vereinfachung der Induktionsvariablen fälschlicherweise nuw zu add nuw i64 %x, -1 hinzugefügt hat. x ist das Argument für die Funktion, und nuw bedeutet kein vorzeichenloser Wrap. Dies bestätigt also effektiv, dass das Argument 0 ist, an einem Punkt in der Funktion, an dem dies nicht garantiert ist.

Bisecting (Bearbeiten: von LLVM 9 bis LLVM 10, von dem @tmiasko sagte, dass es nicht betroffen ist) erzeugt dieses Commit:

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

Sieht vielversprechend aus, da r366419 (das Commit, das durch das obige Commit zurückgesetzt wird) in der von Rust verwendeten LLVM 9.0-Verzweigung enthalten ist.

Alle 38 Kommentare

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

Das Release-Team erwägt, eine Punktveröffentlichung für Rust 1.41 zu machen (wir haben sie in der Sitzung der letzten Woche kurz besprochen), und ich würde es begrüßen, wenn dies bald aufgenommen wird, wenn wir bald eine PR veröffentlichen können.

Hey LLVM ICE-Breaker! Dieser Fehler wurde als gut identifiziert
"LLVM ICE-brechender Kandidat". Für den Fall, dass es nützlich ist, hier einige
[Anweisungen] zur Behebung dieser Art von Fehlern. Vielleicht einen Blick darauf werfen?
Vielen Dank! <3

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

Wenn Sie es mit Cargo Run ausführen - Release mit der stabilen Version 1.41.0 druckt ungefähr so ​​etwas (getestet unter MacOS 10.15 und Ubuntu 19.10):

Ich kann das auf dem Spielplatz nicht reproduzieren . Das Programm funktioniert dort unter 1.41.0 im Release-Modus einwandfrei.

EDIT: Ah, das hast du schon gesagt.
Auch das Programm ist in Miri in Ordnung, so dass dies wahrscheinlich nicht UB ist, sondern eine Fehlkompilierung.

Nur um einen Datenpunkt hinzuzufügen, kann ich dies unter Linux mit der neuesten Nacht reproduzieren:

[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

Ich konnte das oben genannte mit genau der gleichen Ausgabe mit Rust 1.41 stabil reproduzieren. Rost 1.40 stabil zeigt nicht das Problem:

$ ./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.

Ich denke, dass dies alles mit dem Bericht von @dfyz übereinstimmt, außer dass dies zumindest bestätigt, dass es nicht macOS-spezifisch ist.

  • cargo-bisect-rustc sagt, dass die Regression zum ersten Mal in der nächtlichen 2019-12-12 stattgefunden hat, speziell in diesem Commit . Dies erscheint mir verdächtig, da 1.40.0 , das das Problem nicht aufweist, nach diesem Datum veröffentlicht wurde.

Dies wird erwartet. 1.40.0 wurde am 19.12.2019 veröffentlicht, basierend auf dem damaligen Zweig beta , der sechs Wochen zuvor (ungefähr zum Zeitpunkt der Veröffentlichung von 1.39.0) von master verzweigt wurde. Weitere Informationen zu Release-Kanälen finden Sie unter https://doc.rust-lang.org/book/appendix-07-nightly-rust.html .

Wenn ich raten müsste, würde ich sagen, dass https://github.com/rust-lang/rust/pull/67015 der wahrscheinliche Schuldige ist. Diese 3 Codegen-Probleme wurden behoben, sodass Codegen-kritischer Code berührt wird.
Cc @ osa1 @ oli-obk @wesleywiser

Danke für den Ping. Ich mache gerade einen Build, um nachzuforschen. Es ist möglich, dass dies ein Fehler ist, der mit # 67015 eingeführt wurde. Eine andere Möglichkeit ist, dass # 67015 gerade einen vorhandenen Fehler aufgedeckt hat.

Ich habe es geschafft, den Segfault mit rustc nightly unter Linux zu reproduzieren, aber nicht mit meinem Build, der mit dieser config.toml generiert wurde:

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]

Mit rustc nightly habe ich MIRs vor und nach ConstProp überprüft und die MIRs sind identisch. Wenn dies also durch ConstProp verursacht wird, liegt dies an einem Unterschied im generierten Code einer Bibliothek, nicht an diesem Programm.

Regression in 033662dfbca088937b9cdfd3d9584015b5e375b2

@rustbot Etiketten ändern: -E-braucht-Halbierung


@ osa1 das debug-assertions = true ist wahrscheinlich schuld. Wenn ich versuche, das Programm mit -C debug-assertions=y (mit einem Vanilla Nightly Compiler) zu kompilieren, gerät das Programm in Panik anstelle des Segfault

Ich glaube ich habe es gelöst! Das Zurücksetzen von a983e0590a43ed8b0f60417828efd4e79b51f494 behebt das Problem. Es schien mir den ganzen Tag der Schuldige zu sein, aber ich konnte es bei der Arbeit nicht testen :) Kann mir jemand helfen, wie ich am besten eine PR für dieses Problem machen kann? Ich denke, der beste Weg wäre, einen Testfall hinzuzufügen, der fehlschlagen muss, aber es scheint, dass dies sehr plattformspezifisch ist usw. Vielleicht ist das also doch keine gute Idee? Irgendwelche Ideen hier? Vielen Dank!

(Dies wurde bereits vom OP halbiert)

Ich habe es geschafft, dies mit einem lokalen Build zu reproduzieren, bei dem debug-assertions deaktiviert war (danke für das @ hellow554).

Einige der PRs im Rollup verursachen Konflikte, wenn sie zurückgesetzt werden, da wir seitdem alles mit rustfmt , aber ich glaube, dieses Problem liegt an # 67174.

edit: anscheinend haben wir das gleichzeitig gefunden @shahn :)

@lqd Ja, dies ist das Problem, das das Commit enthält, auf das ich oben verwiesen habe. Das ist der Schuldige.

Um einen weiteren Datenpunkt hinzuzufügen, verschwindet das Problem, wenn codegen-units auf 3 oder weniger gesetzt ist (unter der Annahme eines Release-Profils mit incremental=false ). Mit anderen Worten, ich kann reproduzieren, wenn codegen-units 4 oder höher ist.

Der Aufruf von panic_bounds_check verschwindet nach dem Passieren des LLVM Jump Threading. Ich kann das Problem mit opt ​​von LLVM 9 reproduzieren, aber nicht von LLVM 10.

Also habe ich ausgecheckt und eine Stufe 1 rustc ( ./x.py build -i --stage 1 ) erstellt, libstd ( ./x.py build -i --stage 1 --keep-stage 0 src/libstd ) ohne # 67174 neu erstellt und das Segfaulting-Programm mit neu kompiliert vier Codegen-Einheiten ( rustc +stage1 -C opt-level=3 -C codegen-units=4 main.rs ). Wie erwartet verschwand der Segfault dadurch. Wenn ich # 67174 erneut anwende, kehrt der Segfault zurück.

Das heißt, ich habe jetzt zwei Compiler, die sich nur in der von ihnen verwendeten Standardbibliothek unterscheiden. Nennen wir diese Compiler GOOD (kein Segfault) und BAD (Segfault).

Ich bemerkte dann, dass die 4 _unoptimierten_ *.ll Dateien, die von GOOD ( -C no-prepopulate-passes ) generiert wurden, ziemlich gleich denen sind, die von BAD generiert wurden (der einzige Unterschied) Ich habe gesehen, dass die verschiedenen zufälligen IDs in Funktionsnamen vorhanden sind, aber die optimierten *.ll -Dateien (keine -C no-prepopulate-passes ) sind sehr unterschiedlich. Ich bin in keiner Weise ein Compiler-Experte, aber da das zu kompilierende Programm in beiden Fällen genau das gleiche ist, sind beide eigentlichen Compiler genau gleich, und der einzige Unterschied besteht in einer vorkompilierten Standardbibliothek. Ich denke, LTO könnte beteiligt sein .

In der Tat, wenn ich eines von -Z thinlto=no , -C lto=no , -C lto=yes , -C lto=thin (an diesem Punkt bin ich mir wirklich nicht sicher, aber ich denke, alle Formen von -C lto unterscheiden sich von ThinLTO (das standardmäßig verwendet wird) bis BAD , der Segfault verschwindet wieder.

Ist es wahrscheinlich, dass LTO hier schuld ist?

Ich habe den Testfall gelesen. Ich habe das Commit gelesen, das zurückgesetzt wird. Ich habe immer noch nicht die geringste Ahnung, was los ist. Was ist kaputt gegangen?

Was ist kaputt gegangen?

Ich glaube zu diesem Zeitpunkt nicht, dass irgendjemand mit Sicherheit sagen kann, was genau kaputt gegangen ist, aber meine vorläufige Analyse lautet: Bitte nehmen Sie Folgendes mit einem Körnchen Salz, ich bin weder im Rust-Team noch im LLVM-Team, alles was ich bin kann tun, ist mit dem Compiler zu basteln und auf die LLVM IR zu starren):

  • Wir haben Überlaufprüfungen aus einer einzelnen Zeile in Layout::repeat() aus der Standardbibliothek entfernt, was schließlich zu dieser Speichersicherheit führte. Mathematisch gesehen sollte die Verwendung der ungeprüften Addition hier absolut sicher sein - der Kommentar in dieser Funktion (und auch in Layout::pad_to_align() ) erklärt, warum;
  • Mein Codebeispiel, das das Problem demonstriert, ruft diese Funktion nicht einmal auf, verwendet jedoch explizit Vec , das implizit Vec::reserve_internal() , was wiederum Layout::repeat() aufruft .
  • Layout::repeat() ist als #[inline] markiert, und anscheinend besteht der einzige relevante Unterschied zwischen GOOD und BAD darin, ob diese Funktion in do_test() oder eingebunden ist nicht. Das Wiederherstellen von Überlaufprüfungen verhindert beispielsweise das Inlining und behebt das Problem. Das Entfernen des Attributs #[inline] bewirkt den gleichen Effekt. Durch Deaktivieren von LTO wird das Inlining für Bibliotheksfunktionen deaktiviert und das Problem erneut behoben.

Wenn dies zutrifft (auch hier bin ich mir bei keinem der oben genannten Punkte zu 100% sicher), bedeutet dies, dass ein unerwünschter LLVM-Durchgang oder eine Kombination von Durchgängen die IR nach dem Inlining falsch optimiert. Das ist es, was ich derzeit zu untersuchen versuche, aber leider ist es nicht einfach (zumindest für einen Nicht-LLVM-ICE-Breaker wie mich), da die IR-Unterschiede zwischen GOOD und BAD mäßig sind groß. Es sieht so aus, als würde die schlechte Version panic_bounds_check weglassen, aber ich bin mir immer noch nicht ganz sicher, warum.

Inspiriert von @tmiaskos Kommentar habe ich versucht, rustc mit verschiedenen LLVM-Versionen zu kompilieren, und es scheint, dass LLVM 10rc1 das Problem behebt (die letzte fehlerhafte LLVM-Version, die ich ausprobiert habe, ist 9.0.1). Es sieht jedoch nicht so aus, als wäre der Jump Threading- Pass schuld, da ich keine relevanten Commits zwischen 9.0.1 und 10rc1 sehe.

Wenn das Zurücksetzen der Layout::repeat() -Änderung nur ein Symptom für ein bestimmtes Auftreten eines nicht verwandten Fehlers verbirgt, ist das Zurücksetzen wirklich das Richtige?

Wenn das Zurücksetzen der Änderung von Layout :: repeat () nur ein Symptom für ein bestimmtes Auftreten eines nicht verwandten Fehlers verbirgt, ist das Zurücksetzen wirklich das Richtige?

Ich denke, es kann in Ordnung sein, wenn:

  • Die Änderung wird versendet
  • Dies erleichtert das Auslösen des Fehlers erheblich und betrifft viele Benutzer
  • Die ordnungsgemäße Reparatur dauert lange

Wenn diese gelten, würde ich die Änderung rückgängig machen, eine kleinere Version versenden, um Benutzer zu entsperren (ohne die Änderung funktionierte alles einwandfrei, obwohl der Fehler noch vorhanden war) und mich dann auf den eigentlichen Fehler konzentrieren.

In einem anderen Compiler erinnere ich mich, dass ich das tatsächlich getan habe. Wir haben eine Änderung in einem Release-Zweig rückgängig gemacht, aber nicht auf dem Master (was keine gute Praxis ist, da es später Probleme verursachte) und eine neue Nebenversion ausgeliefert. Dann wurde der eigentliche Fehler behoben.

Auf jeden Fall sehe ich keine Probleme beim Zurücksetzen, solange der Fehler priorisiert und behoben wird und das Festschreiben, das das Auslösen des Fehlers erheblich erleichtert, selbst keine Fehlerbehebung ist.

Eine Frage ist also, ist dieser Fehler wirklich leicht auszulösen? Bisher hatten wir einen Bericht mit einem etwas komplizierten Testfall, in dem der Versuch, weiter zu minimieren (z. B. das Abrollen einer scheinbar trivialen for _ in 0..1 -Schleife), nicht reproduziert werden kann.

Ich denke nicht, dass der Fehler leicht auszulösen ist, es sieht so aus, als hätte ich gerade besonders Pech gehabt.

Wie auch immer, ich schätze die Mühe, die @shahn durchgemacht hat , um die Layout::new() -Änderung rückgängig zu machen, aber IMO, die es rückgängig macht, ist in diesem Fall nicht das Richtige. Meine Argumentation (zusätzlich zu dem, was @SimonSapin gesagt hat):

  • Durch das Entfernen von Überlaufprüfungen in Layout::repeat() kann LLVM Vec::reserve() in Release-Builds einbinden. In einigen Fällen kann dies zu einer guten Leistungssteigerung führen (obwohl dies natürlich gemessen werden sollte).
  • Im wahrsten Sinne des Wortes verwendet die vorherige Funktion in libcore/alloc.rs ( Layout::pad_to_align() ) dasselbe Muster der ungeprüften Addition mit genau demselben Kommentar, der erklärt, was dies möglich macht. Das Wiederherstellen der Überlaufprüfungen in Layout::repeat() aber nicht in Layout::pad_to_align() scheint mir wirklich seltsam;
  • Wenn jemand in diesem Problem wirklich blockiert ist (ich bin es definitiv nicht), gibt es viele andere Problemumgehungen, die keine stdlib-Änderungen beinhalten (z. B. ThinLTO deaktivieren, Optimierungsstufe ändern, Anzahl der Codegen-Einheiten verringern).

Vielleicht lokal eine Release-eingeschlossene defensive Behauptung der Invariante als Vorbedingung einwerfen, damit sie mit bestimmten Details in Panik gerät, um diesen bestimmten Randfall oder einen Debugger-Fu aufzuspüren? Ich würde wetten, dass es eine ungeprüfte Berechnung ist, die unter bestimmten Bedingungen durchkommt.

Wenn es dann aufgespürt wird (irgendwo in LLVM, wie ich gerade erfahren habe, ty @dyfz), wäre ein Regressionstestfall fantastisch, damit es nicht wieder vorkommt. 🙏

LLVM-IR-Wiedergabegerät: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

Führen Sie opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . Wenn das Innere der Klammern i64 -1 , ist die fehlerhafte Optimierung aufgetreten. Wenn es nicht ... ist es vielleicht nicht, aber es ist schwer sicher zu sein.

Es scheint von LLVM zu stammen, das im Rahmen des Durchlaufs zur Vereinfachung der Induktionsvariablen fälschlicherweise nuw zu add nuw i64 %x, -1 hinzugefügt hat. x ist das Argument für die Funktion, und nuw bedeutet kein vorzeichenloser Wrap. Dies bestätigt also effektiv, dass das Argument 0 ist, an einem Punkt in der Funktion, an dem dies nicht garantiert ist.

Bisecting (Bearbeiten: von LLVM 9 bis LLVM 10, von dem @tmiasko sagte, dass es nicht betroffen ist) erzeugt dieses Commit:

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

Sieht vielversprechend aus, da r366419 (das Commit, das durch das obige Commit zurückgesetzt wird) in der von Rust verwendeten LLVM 9.0-Verzweigung enthalten ist.

T-Compiler-Triage: P-Medium, basierend auf der folgenden Zusammenfassung der Situation:

pnkfelix: Es scheint, als wären die verbleibenden Arbeitselemente für # 69225 1. LLVM reparieren (entweder durch Auswahl der 58e8c793d0e43150a6452e971a32d7407a8a7401 oder durch Upgrade auf LLVM 10) und dann 2. Readd PR # 67174.
pnkfelix: jedoch fällt mir keines davon als vorrangiges Element auf.
pnkfelix: Zumindest scheint dieser LLVM-Fehler nicht besser oder schlechter zu sein als andere LLVM-Codegen-Fehler. Was ich denke, ist das, was @simulacrum gerade gesagt hat.

Update: In PR # 67759 wird ein Upgrade auf LLVM 10 versucht

Update 2: Es ist möglicherweise unklug, das Revert-Commit blind zu wählen, da wir das Original vermutlich aus irgendeinem Grund ausgewählt haben und das Revert daher unbeabsichtigte Downstream-Effekte haben könnte. Zumindest sollten wir es nicht versuchen, ohne die Konsequenzen zu verstehen (und angesichts des Aufwands für ein Upgrade auf LLVM 10 sollten wir wahrscheinlich überhaupt nicht versuchen, den Revert auszuwählen, da dies größtenteils verschwendeter Aufwand wäre ...)

Wurde das ursprüngliche Commit ausgewählt? Zumindest aus dem Kommentar von @comex , der mir nicht klar ist ("ist in der von Rust verwendeten LLVM 9.0-Verzweigung enthalten" könnte auch bedeuten, dass es nur ein Teil von LLVM 9.0 ist).

Das fragliche Commit ist eine sehr lokale und kleine Änderung , die einem Funktionsaufruf einen einzelnen Parameter hinzufügt und buchstäblich it is safe [in this case] to add SCEV::FlagNSW sagt (und nach dem Code kann der neue Parameter auch SCEV::FlagNUW ). Daher halte ich es für sehr wahrscheinlich, dass genau dies die Fehloptimierung verursacht. Ich kann bestätigen, dass das Entfernen dieses Parameters (dh das Ändern von (void)getAddRecExpr(getAddExpr(StartVal, Accum, Flags), Accum, L, Flags); in (void)getAddRecExpr(getAddExpr(StartVal, Accum), Accum, L, Flags); ) das Problem behebt.

Darüber hinaus wurde dieses problematische Commit nicht ausgewählt. Es ist nur Pech - es sieht so aus, als ob die Wiederherstellung nach der Erstellung von 9.0.0 stattgefunden hat, sodass Upstream 9.0.0 immer noch den fehlerhaften Parameter hat. Das Zurücksetzen wurde aus irgendeinem Grund auch nicht auf 9.0.1 zurückportiert. 10.0.0-rc1 und spätere Versionen haben das Zurücksetzen.

Hier ist ein Kommentar , der erklärt, warum es in der Tat nicht sicher ist, hier nsw oder nuw hinzuzufügen. Es ist wahrscheinlich eine gute Idee, mit einem LLVM-Entwickler darüber zu sprechen, aber ich denke, dass die Auswahl des Reverts dieses Problem beheben und überhaupt keine unbeabsichtigten Auswirkungen haben wird, da es so klein und in sich geschlossen ist.

PS Ein großes Lob an Ursache . Fantastischer Job.

FWIW Ich kann bestätigen, dass https://github.com/llvm/llvm-project/commit/58e8c793d0e43150a6452e971a32d7407a8a7401 sicher ist, eine konservative Änderung vorzunehmen. Siehe auch https://lists.llvm.org/pipermail/llvm-dev/2019-September/135195.html, wenn Sie mehr Kontext bezüglich des Problems mit SCEV-Nowrap-Flags benötigen.

Ich glaube, ich habe gerade einen Weg gefunden, das Problem zu reproduzieren, selbst nachdem ich # 67174 zurückgesetzt habe. Hier ist ein etwas längeres, aber immer noch sicheres Programm, das unter Windows, Linux und MacOS zuverlässig die neuesten nächtlichen Daten mit # 67174 zurücksetzt:

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)

Mac OS:

λ 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

Dieses Programm hängt nicht von der Anzahl der CODEGEN Einheiten, so dass es im Spielplatz Segfaults, auch (auf stabile, Beta- und jede Nacht). Ich habe dies auch reproduziert, indem ich rustc vom Master (mit # 67174 zurückgesetzt) ​​kompiliert habe, der mit LLVM 9 verknüpft ist.

Der zugrunde liegende LLVM-Fehler ist immer noch derselbe. Wenn Sie also auf LLVM 10 aktualisieren oder den LLVM-Fix auswählen, wird der Segfault behoben.

Ich wünschte wirklich, ich hätte verstanden, was besser läuft. Es sieht so aus, als ob die Grenzprüfungen aufgrund der zusätzlichen nuw aufgehoben werden, die von falsch zwischengespeicherten SCEV-Werten stammen (genau wie im C-Programm des mit @nikic verknüpften Threads). Aber bis die schlechte Optimierung eintritt, kann ich mein einfaches Programm durch die Schichten der LLVM-Basisblöcke kaum wiedererkennen. Schlimmer noch, jede scheinbar no-op-Änderung im Quellcode (z. B. Entfernen der Variablen cnt ) führt zu einem sehr unterschiedlich aussehenden LLVM-IR und lässt das Problem verschwinden.

Mein Eindruck ist, dass 1.41.1 gerade in # 69359 finalisiert wurde (schlechtes Timing meinerseits), daher kann derzeit nicht viel getan werden. Ist es zumindest eine gute Idee, den Kommentar in Layout::repeat() mit einer detaillierteren Erläuterung des LLVM-Problems zu aktualisieren? Wenn ja, kann ich eine PR senden.

Mein Eindruck ist, dass 1.41.1 gerade in # 69359 finalisiert wurde (schlechtes Timing meinerseits), daher kann derzeit nicht viel getan werden.

Wenn der in 1.41.1 enthaltene Patch das Problem nicht wirklich behebt, sollten wir uns überlegen, ob wir den neuen Fix zurückportieren und die Version neu erstellen möchten. In der Sitzung des Release-Teams herrschte Konsens darüber, den LLVM-Fix nicht zurück zu portieren, aber ich persönlich denke, dass ein anderer dieser neuen PoC eine weitere Diskussion zu diesem Thema rechtfertigen könnte.

cc @ Mark-Simulacrum @ rust-lang / release

@dfyz Wir werden versuchen, einen weiteren Build von 1.41.1 mit dem zurückportierten LLVM-Fix zu erhalten, während wir auf einen Konsens über den tatsächlichen Versand warten.

FWIW, für mich funktioniert der neue Reproducer wie erwartet ( index out of bounds ) mit stabilem 1.38.0 und früher, aber Segfaults mit 1.39.0 und höher. Es gibt keinen großen Unterschied in der LLVM zwischen 1,38 und 1,39 (https://github.com/rust-lang/llvm-project/compare/71fe7ec06b85f612fc0e4eb4134c7a7d0f23fac5...8adf9bdccfefb8d03f0e8db3) auf dem Weg auch.

Der neue Reproducer arbeitet wie erwartet (Index außerhalb der Grenzen) auf stabilem 1.38.0

Ich habe (aus Versehen) herausgefunden, dass das Setzen von -C codegen-units=1 auf 1.38.0 den Segfault reproduziert. 1.37.0 scheint mir sicher zu sein (keine Kombination von Optionen, die ich ausprobiert habe, führt zu einem Segfault).

Ignorieren Sie, dass 1.37.0 LLVM 8 verwendet.
Seltsamerweise ist der LLVM-IR-Unterschied zwischen 1.37.0 und 1.38.0 (mit -C codegen-units=1 ) nur eine Zeile:

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

(wobei %70 aus dem Ergebnis von <core::slice::IterMut<T> as core::iter::traits::iterator::Iterator>::next() )

Dies allein reicht aus, um LLVM dazu zu bringen, die gefürchteten nuw zu add nuw i64 %x, -1 hinzuzufügen.

1.37.0 scheint mir sicher zu sein (keine Kombination von Optionen, die ich ausprobiert habe, führt zu einem Segfault).

Da LLVM 8 verwendet wird, sollte die beschuldigte SCEV-Änderung überhaupt nicht existieren.

Das ist mit LLVM 8

Meine schlechte Entschuldigung für die Verwirrung (ich war so glücklich, sie auf ein einzeiliges Diff zu reduzieren, dass ich mir nicht einmal die Mühe gemacht habe, die LLVM-Version zu überprüfen).

Wir haben neue 1.41.1-Artefakte mit dem darin enthaltenen LLVM-Fix vorbereitet. Sie können sie lokal testen mit:

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

Ping in https://github.com/rust-lang/rust/issues/69225#issuecomment -586941455

[Triagebot] Das Problem wurde erfolgreich gelöst, ohne dass das Ping-Compiler-Team daran beteiligt war.
Bretty gut.

1.41.1 ist raus, ich denke es ist Zeit, dieses Problem endlich zu schließen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen