Rust: Bringen Sie rustc bei, Tail Calls zu machen

Erstellt am 27. Jan. 2011  ·  18Kommentare  ·  Quelle: rust-lang/rust

Rustc weiß noch nicht, wie man Tail-Calls ('be' statt 'ret') macht. Es sollte nicht zu schwer sein, ihm beizubringen, wie.

A-LLVM

Hilfreichster Kommentar

Wir haben Pläne, nach Möglichkeit garantierte TCO zu implementieren. Wir haben ihm sogar ein Stichwort "werden" reserviert. Bitte überprüfen Sie das RFCs-Repo.

Am 3. August 2016, 19:46 -0400, schrieb Antoine PLASKOWSKI [email protected] :

Ich bin neu in Rost und ich bin sehr traurig. Ich versuche eine rekursive Tail-Funktion und stapele so schnell über. Am schlimmsten ist es, wenn ich das Verhalten beim Kompilieren im Release ändere. Er ruft nur nicht meine Funktion an. Ein Freund sagte zu mir, dass die LLVM-Optimierung auf undefinierten Wert (LLVM weiß, dass das eine Schwanzrekursion unendlich ist?!?).

fn rec(i: i32) { rec(i + 1) } fn main() { println!("{}", rec(0)); }

Ich stimme Boiethios zu, einer Sprache mit funktionalen Merkmalen, die Tail-Call-Miss etwas nicht implementiert.

Ich schreibe in C und C++, sie garantieren keinen Tail-Call, aber tatsächlich handhaben sie es sehr gut. Es wird mich nicht davon abhalten, Rust zu lernen, jetzt weiß ich, dass Rust nicht damit umgehen kann, ich werde ohne programmieren, also ist es kein großes Problem.

Aber ich denke, das ist ein sehr gutes Feature für eine moderne Sprache.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub an (https://github.com/rust-lang/rust/issues/217#issuecomment-237409642) oder schalten Sie den Thread stumm (https://github.com/notifications/unsubscribe -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_).

Alle 18 Kommentare

Laut Graydon müssen wir uns noch etwas Gedanken über die Aufrufkonventionen machen, bevor wir dies implementieren. LLVM erfordert die Fastcc-Konvention, um Tail-Aufrufe zu implementieren, aber es macht nicht ganz das, was wir wollen:

Graydon: brson: den Kommentaren im llvm-land nach zu urteilen, könnten wir in Schwierigkeiten stecken. wir können vielleicht kompensieren, was fastcc heute tut, aber morgen darf es seine Meinung ändern.
Graydon: Ich dachte fastcc == x86_fastcall, aber ich lag falsch.
Graydon: Das sind verschiedene Aufrufkonventionen. Fastcc ist "wie auch immer sich llvm diese Woche anfühlt"
Graydon: Wir müssen zu x86_fastcall wechseln und dann längerfristig unsere eigene Aufrufkonvention schreiben.

Dies ist derzeit aufgrund einiger Komplikationen in der Art und Weise, wie LLVM Tail Calls behandelt, zur Hälfte implementiert. Rustc analysiert 'be'-Ausdrücke und übersetzt sie als call+ret-Paare, was ausreicht, um uns dazu zu bringen, an einfachen, nicht sehr tiefen Fällen zu 'arbeiten'. Könnte zum Bootstrap reichen.

Es gibt anscheinend nur einen CC (fastcc), der garantierte Tail-Calls unterstützt, und um uns daran anzupassen, müssen wir unsere ABI-Annahmen an mehreren Stellen ändern (es wird zu callee-restore, wenn Sie das Flag -tailcallopt aktivieren). Selbst wenn wir also unsere call+ret-Paare wie erforderlich mit „tail“ annotieren, können wir LLVM noch nicht anweisen, mit der Optimierung zu beginnen.

Es stellte sich heraus, dass nicht mehr Implementierung erforderlich ist, als hier für das Selbsthosten gezeigt wird. Punting zum nächsten Meilenstein.

Hat jemand das Gefühl, dass wir das tatsächlich noch tun werden?

Es scheint nicht so, als würde es passieren.

Wie sieht es mit LLVM und Tail Calls aus? Wenn sie dazu gebracht werden können, ohne irgendwelche invasiven Dinge wie das Deklarieren des Angerufenen als Tail-Call zuverlässig zu funktionieren, könnten wir eine begrenzte Menge von Dingen definieren, die an Tail-Calls übergeben werden können (eigene Argumente des Aufrufers, Skalare) und Fehler, wenn etwas passiert sonst wird bestanden. Solche Tail-Calls sind in der Praxis jedoch möglicherweise nicht sehr nützlich.

Sie arbeiten nur mit einer Angerufenen-ABI, die in Nicht-Tail-Call-Fällen suboptimal ist. Also in gewissem Sinne, ja, sie verlangen, dass der Angerufene deklariert wird, dass er als Schwanz angerufen wird.

Wir könnten dies beispielsweise implementieren, indem wir eine Kiste analysieren und, wenn wir eine Funktion finden, die tail aufgerufen wird, sie entweder separat unter der Friendly-to-Tail-Call-ABI kompilieren oder Wrapper kompilieren, die ABI beim Eintritt wechseln, oder so. Oder wir können alternativ _jede_ Funktion überall auf die Verwendung des Tail-Call-freundlichen ABI umstellen. Ich bin mir nicht sicher, ob wir das derzeit tun. Wir waren für eine Weile, aber wir haben vielleicht aufgehört. Es ist die LLVM „Fastcall“ ABI, von der ich glaube, dass wir sie nicht mehr verwenden.

Auf jeden Fall ist es ein bisschen chaotisch.

Wir haben jetzt Geschwisterrufoptimierung. Planen wir zu versuchen, mehr zu tun?

Wird nicht passieren, außer in dem Umfang, der von #2216 angesprochen wird. Die Lösung für #2216 sollte breit genug sein, um die allgemeine Zustandsmaschinencodierung zu unterstützen.

Dies kommt immer wieder in Gesprächen vor, und wir haben wieder be reserviert. Wiedereröffnung.

Ich denke, Graydons Kommentar auf der Mailingliste:

https://mail.mozilla.org/pipermail/rust-dev/2013-April/003557.html

steckt einen Nagel in diesen Sarg. Hier wiedergegeben:


Am 04.10.2013 um 5:43 Uhr schrieb Artella Coding:

Hi, macht Rust Tail-Call-Optimierung? Der Grund, warum ich frage, ist das
Die folgende rekursive Implementierung eines Endaufrufs führt zu einem Stack
Überlauf. Danke.

Nein, und das wird es sehr wahrscheinlich nicht. Wir haben hier einen langjährigen Fehler:

https://github.com/mozilla/rust/issues/217

sowie eine Wiki-Seite und mehrere Mailinglisten-Threads:

https://github.com/mozilla/rust/wiki/Bikeshed-tailcall
https://mail.mozilla.org/pipermail/rust-dev/2011-August/000689.html
https://mail.mozilla.org/pipermail/rust-dev/2012-January/001280.html
...

Die Zusammenfassung all dessen lautet:

  • Wir alle wissen, dass Tail Calls ein tugendhaftes Sprachmerkmal sind.
    Eine weitere Ausarbeitung ihrer Tugenden ist nicht erforderlich. Viele von uns
    Lisp- und ML-Hintergrund haben und sie sehr mögen würden. Ihr
    Abwesenheit ist Kummer und Traurigkeit, nicht leichtfertig erreicht.
  • Tail Calls "spielen schlecht" mit deterministischer Zerstörung. Einschließlich
    deterministischer Drop von ~ Boxen. Um nicht zu sagen, dass sie es nicht sind
    zusammensetzbar, aber die Optionen zum Zusammenstellen sind UI-umständlich,
    leistungsbeeinträchtigend, semantikkomplizierend oder alles zusammen.
  • Tail-Calls "spielen auch schlecht" mit Annahmen in C-Tools, einschließlich
    Plattform-ABIs und dynamisches Linken.
  • Tail Calls erfordern eine Calling-Konvention, die ein Performance-Hit ist
    relativ zur C-Konvention.
  • Wir finden, dass die meisten Fälle von tail _recursion_ einigermaßen gut zu konvertieren sind
    Schleifen und die meisten Fälle von nicht-rekursiven Endaufrufen codieren den Zustand
    Maschinen, die sich einigermaßen gut in umwickelte Schleifen umwandeln lassen
    Aufzählungen. Keine von diesen sind _quite_ so hübsch wie die
    Tail-Call-Using-Varianten, aber sie funktionieren und sind "ebenso schnell"*,
    sowie idiomatisch für C- und C++-Programmierer (die unsere
    Hauptpublikum).

Es tut mir leid, das alles sagen zu müssen, und es ist schweren Herzens, aber wir
versucht und keinen Weg gefunden, die damit verbundenen Kompromisse einzugehen
summieren sich zu einem Argument für die Einbeziehung in Rost.

-Graydon

  • In Bezug auf die Geschwindigkeit ist eine Switch-in-a-Loop-Zustandsmaschine also ein indirekter Versand
    wird wahrscheinlich langsamer laufen als eine durch Tail-Aufrufe codierte Zustandsmaschine
    Staat zu Staat; auf der anderen Seite, wenn der Weg, um diese Leistung zu bekommen
    "zurück" bedeutet, Tail Calls überall einzuschalten, wir würden einen isoliert handeln
    Suboptimal-perf-Fall für eine programmübergreifende Suboptimal-perf-Steuer. Wir
    finde diesen Handel nicht akzeptabel.

Es ist vielleicht erwähnenswert, dass Haskell normalerweise eine statische Argumenttransformation für Inlining, Fusion usw. benötigt. http://stackoverflow.com/a/9660027/667457

Rust könnte die statische Argumenttransformation unterstützen, indem er sagt, dass Funktionen, die in einer anderen Funktion definiert sind, für Tailcall-Optimierungen in Frage kommen sollten. Oder warnen Sie einfach, wenn Tailcalls zwischen Funktionen, die in einer anderen Funktion verschachtelt sind, bei der Geschwisteroptimierung fehlgeschlagen sind. Nicht sicher, die aktuelle Situation.

Es ist traurig, dass die Schwanzrekursion nicht implementiert wird. Dann habe ich eine Frage: Warum eine Sprachsyntax erstellen, die wie eine funktionale Sprachsyntax aussieht, wenn ein wesentliches Merkmal von funktional fehlt?

Ich bin neu in Rost und ich bin sehr traurig. Ich versuche eine rekursive Tail-Funktion und stapele so schnell über. Am schlimmsten ist es, wenn ich das Verhalten beim Kompilieren im Release ändere. Er ruft nur nicht meine Funktion an. Ein Freund sagte mir, dass die LLVM auf einen undefinierten Wert optimiert wird (LLVM weiß, dass das eine Schwanzrekursion unendlich ist?!?).

fn rec(i: i32) {
  rec(i + 1)
}

fn main() {
  println!("{}", rec(0));
}

Ich stimme Boiethios zu, einer Sprache mit funktionalen Merkmalen, die Tail Call nicht implementiert und etwas vermisst.

Ich schreibe in C und C++, sie garantieren keinen Tail-Call, aber tatsächlich handhaben sie es sehr gut. Es wird mich nicht davon abhalten, Rost zu lernen, jetzt weiß ich, dass Rost nicht damit umgehen kann. Ich werde ohne codieren, also ist es kein großes Problem.

Aber ich denke, das ist ein sehr gutes Feature für eine moderne Sprache.

beachte das

fn rec(i: i32) {
  println!("Hello");
  rec(i + 1)
}

Stapelüberlauf auch im Freigabemodus der Ladung

Wir haben Pläne, nach Möglichkeit garantierte TCO zu implementieren. Wir haben ihm sogar ein Stichwort "werden" reserviert. Bitte überprüfen Sie das RFCs-Repo.

Am 3. August 2016, 19:46 -0400, schrieb Antoine PLASKOWSKI [email protected] :

Ich bin neu in Rost und ich bin sehr traurig. Ich versuche eine rekursive Tail-Funktion und stapele so schnell über. Am schlimmsten ist es, wenn ich das Verhalten beim Kompilieren im Release ändere. Er ruft nur nicht meine Funktion an. Ein Freund sagte zu mir, dass die LLVM-Optimierung auf undefinierten Wert (LLVM weiß, dass das eine Schwanzrekursion unendlich ist?!?).

fn rec(i: i32) { rec(i + 1) } fn main() { println!("{}", rec(0)); }

Ich stimme Boiethios zu, einer Sprache mit funktionalen Merkmalen, die Tail-Call-Miss etwas nicht implementiert.

Ich schreibe in C und C++, sie garantieren keinen Tail-Call, aber tatsächlich handhaben sie es sehr gut. Es wird mich nicht davon abhalten, Rust zu lernen, jetzt weiß ich, dass Rust nicht damit umgehen kann, ich werde ohne programmieren, also ist es kein großes Problem.

Aber ich denke, das ist ein sehr gutes Feature für eine moderne Sprache.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub an (https://github.com/rust-lang/rust/issues/217#issuecomment-237409642) oder schalten Sie den Thread stumm (https://github.com/notifications/unsubscribe -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_).

Nur eine Frage: Es ist hypothetisch möglich, Call-Graph-Analysen durchzuführen und Tail-Calls in Schleifen umzuwandeln, zumindest innerhalb einer einzelnen Kiste, aber es ist zur Kompilierzeit rechenintensiv und ziemlich schwierig zu programmieren. Gab es Diskussionen über diese Möglichkeit? Das wäre jetzt noch eine Option, unabhängig von der Wahl der Aufrufkonvention.

Der Ansatz von lbstanza besteht darin, eine Annotation von Funktionen zu verlangen, um sie für TCO in Frage zu stellen (defn+ anstelle von defn). In Rust würde dies den zusätzlichen Aufwand für die Kompilierzeit für den Vorschlag von Timthelion weitgehend verringern, solange Sie nicht buchstäblich überall Schwanzaufrufe verwenden, lol.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen