Rust: Научите rustc выполнять хвостовые вызовы

Созданный на 27 янв. 2011  ·  18Комментарии  ·  Источник: rust-lang/rust

Rustc еще не знает, как делать хвостовые вызовы («be», а не «ret»). Научить этому не должно быть слишком сложно.

Самый полезный комментарий

У нас есть планы в конечном итоге реализовать гарантированную совокупную стоимость владения, если это возможно. Мы даже зарезервировали для него ключевое слово «стать». Пожалуйста, проверьте репозиторий RFC.

3 августа 2016 г., 19:46 -04:00, Антуан ПЛАСКОВСКИЙ , [email protected], написал:

Я новости в ржавчине, и мне очень грустно. Я пробую хвостовую рекурсивную функцию, и я так быстро переполняю стек. худшее, когда я компилирую в выпуске изменение поведения. Он просто не вызывает мою функцию. Друг сказал мне, что LLVM оптимизируется до неопределенного значения (LLVM знает, что хвостовая рекурсия бесконечна?!?).

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

Я согласен с Boiethios, языком с функциональными возможностями, который не реализует хвостовой вызов.

Я пишу на C и C++, они не гарантируют хвостовой вызов, но на самом деле они очень хорошо с этим справляются. Это не помешает мне изучить ржавчину, теперь я знаю, что ржавчина не справляется с этим, я буду кодировать без нее, так что это не большая проблема.

Но я думаю, что это очень хорошая особенность для современного языка.


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую, просмотрите его на GitHub (https://github.com/rust-lang/rust/issues/217#issuecomment-237409642) или отключите ветку (https://github.com/notifications/unsubscribe). -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_).

Все 18 Комментарий

Согласно Грейдону, нам нужно еще немного подумать о соглашениях о вызовах, прежде чем реализовывать это. LLVM требует соглашения fastcc для реализации хвостовых вызовов, но это не совсем то, что нам нужно:

greydon: brson: судя по комментариям в llvm-land, у нас могут быть проблемы. мы могли бы компенсировать то, что fastcc делает сегодня, но завтра ему позволено передумать.
greydon: Я думал, что fastcc == x86_fastcall, но ошибся.
грейдон: это разные соглашения о вызовах. fastcc - это "все, что llvm чувствует на этой неделе"
Graydon: нам нужно переключиться на x86_fastcall, а затем, в долгосрочной перспективе, написать собственное соглашение о вызовах.

В настоящее время это реализовано наполовину из-за некоторых сложностей в том, как LLVM обрабатывает хвостовые вызовы. Rustc анализирует выражения «be» и переводит их в пары call+ret, чего достаточно, чтобы заставить нас «работать» над простыми, не очень глубокими случаями. Может быть достаточно для начальной загрузки.

По-видимому, существует только один CC (fastcc), который поддерживает гарантированные вызовы хвоста, и чтобы адаптироваться к нему, нам нужно изменить наши предположения ABI в нескольких местах (он превращается в callee-restore, когда вы включаете флаг -tailcallopt). Таким образом, даже если мы аннотируем наши пары call+ret как «хвост», как требуется, мы не можем сказать LLVM, чтобы он начал выполнять эту оптимизацию.

Оказалось, что для самостоятельного хостинга не требуется больше реализации, чем показано здесь. Переход к следующей вехе.

Кто-нибудь думает, что мы на самом деле собираемся делать это больше?

Не похоже, что это произойдет.

Как обстоят дела с LLVM и хвостовыми вызовами? Если бы их можно было заставить надежно работать без каких-либо инвазивных вещей, таких как объявление вызываемого объекта хвостовым вызовом, мы могли бы определить ограниченный набор вещей, которые могут быть переданы хвостовым вызовам (собственные аргументы вызывающего объекта, скаляры), и ошибка, когда что-то остальное пройдено. Однако такие хвостовые вызовы могут оказаться не очень полезными на практике.

Возможно ли, что просмотр бэкенда Haskell GHC LLVM может быть полезен?

http://hackage.haskell.org/trac/ghc/wiki/Commentary/Compiler/Backends/LLVM
http://llvm.org/docs/LangRef.html#callingconv
http://llvm.org/docs/CodeGenerator.html#tailcallopt

Они работают только с вызываемым ABI, который является неоптимальным в случаях без хвостового вызова. Так что в некотором смысле да, они требуют, чтобы вызываемый объект был объявлен как хвостовой вызов.

Мы могли бы реализовать это, скажем, проанализировав крейт, и когда мы находим функцию, вызываемую хвостом, либо отдельно скомпилировав ее под дружественным к хвостовому вызову ABI, либо скомпилировав обертки, которые переключают ABI при входе, или что-то в этом роде. Или, в качестве альтернативы, мы можем переключить _every_ функцию везде на использование ABI, удобного для хвостового вызова. Я не уверен, делаем ли мы это сейчас. Мы были на некоторое время, но мы, возможно, остановились. Это LLVM «fastcall» ABI, который, я думаю, мы больше не используем.

В любом случае, это немного беспорядок.

Теперь у нас есть оптимизация одноуровневых вызовов. Планируем ли мы попытаться сделать больше?

Не произойдет, за исключением случаев, указанных в #2216. Решение для #2216 должно быть достаточно широким, чтобы поддерживать кодирование общего конечного автомата.

Это продолжает всплывать в разговорах, и мы снова зарезервировали be . Открытие.

Я думаю, что комментарий Грейдона в списке рассылки:

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

вбивает гвоздь в этот гроб. Воспроизведено здесь:


04.10.2013, 5:43, Artella Coding пишет:

Привет, ржавчина делает оптимизацию хвостового вызова? Причина, по которой я спрашиваю, заключается в том, что
следующая рекурсивная реализация хвостового вызова приводит к стеку
переполнение. Спасибо.

Нет, и, скорее всего, не будет. У нас есть давняя ошибка:

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

а также вики-страницу и несколько веток списка рассылки:

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

Итог всего этого таков:

  • Мы все знаем, что хвостовые вызовы — это полезная функция языка.
    Дальнейшая разработка их достоинств не требуется. Многие из нас
    имеют опыт работы с lisp и ML и очень хотели бы их. Их
    отсутствие — это душевная боль и печаль, не достигнутые легкомысленно.
  • Хвост призывает «играть плохо» с детерминированным разрушением. Включая
    детерминированное удаление ~ ящиков. Не сказать, что они не
    компонуемые, но варианты их компоновки неудобны для пользовательского интерфейса,
    ухудшение производительности, усложнение семантики или все вышеперечисленное.
  • Вызовы Tail также «плохо играют» с предположениями в инструментах C, включая
    ABI платформы и динамическое связывание.
  • Хвостовые вызовы требуют соглашения о вызовах, которое является ударом по производительности.
    относительно соглашения C.
  • Мы находим, что в большинстве случаев хвостовая _recursion_ достаточно хорошо преобразуется в
    циклы, и в большинстве случаев нерекурсивные хвостовые вызовы кодируют состояние
    машины, которые достаточно хорошо преобразуются в циклы, обернутые вокруг
    перечисления. Ни один из них не _совсем_ так красив, как
    варианты с использованием tail-call, но они работают и «такие же быстрые»*,
    а также идиоматические для программистов C и C++ (которые являются нашими
    основная аудитория).

Мне жаль говорить все это, и это с тяжелым сердцем, но мы
пытался и не нашел способ сделать компромиссы, связанные с ними
подведите итог аргументу для включения в ржавчину.

-Грейдон

  • с точки зрения скорости конечный автомат с переключением в цикле является косвенной диспетчеризацией, поэтому
    скорее всего, будет работать медленнее, чем конечный автомат, закодированный хвостовыми вызовами из
    государство к государству; с другой стороны, если способ получить эту производительность
    «назад» — это включить хвостовые вызовы везде, мы будем торговать одним изолированным
    Случай субоптимальной производительности для налога на субоптимальную производительность для всех программ. Мы
    не считаю эту сделку приемлемой.

Возможно, стоит отметить, что Haskell обычно требует преобразования статического аргумента для встраивания, слияния и т. д. http://stackoverflow.com/a/9660027/667457

Rust мог бы поддерживать преобразование статического аргумента, говоря, что функции, определенные внутри другой функции, должны иметь право на оптимизацию хвостового вызова. Или просто предупредите, если хвостовые вызовы между функциями, вложенными в другую функцию, не увенчались успехом при оптимизации родственного элемента. Не уверен в текущей ситуации.

Жаль, что хвостовая рекурсия реализована не будет. Тогда у меня возникает вопрос: зачем создавать синтаксис языка, который выглядит как синтаксис функционального языка, если в нем отсутствует существенная особенность функционала?

Я новости в ржавчине, и мне очень грустно. Я пробую хвостовую рекурсивную функцию, и я так быстро переполняю стек. худшее, когда я компилирую в выпуске изменение поведения. Он просто не вызывает мою функцию. Друг сказал мне, что LLVM оптимизирует до неопределенного значения (LLVM знает, что хвостовая рекурсия бесконечна?!?).

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

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

Я согласен с Boiethios, языком с функциональными возможностями, который не реализует хвостовой вызов.

Я пишу на C и C++, они не гарантируют хвостовой вызов, но на самом деле очень хорошо с этим справляются. Меня не остановит изучение ржавчины, теперь я знаю, что ржавчина с этим не справится. Я буду кодировать без так что это не большая проблема.

Но я думаю, что это очень хорошая особенность для современного языка.

Заметь

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

переполнение стека тоже в режиме выпуска груза

У нас есть планы в конечном итоге реализовать гарантированную совокупную стоимость владения, если это возможно. Мы даже зарезервировали для него ключевое слово «стать». Пожалуйста, проверьте репозиторий RFC.

3 августа 2016 г., 19:46 -04:00, Антуан ПЛАСКОВСКИЙ , [email protected], написал:

Я новости в ржавчине, и мне очень грустно. Я пробую хвостовую рекурсивную функцию, и я так быстро переполняю стек. худшее, когда я компилирую в выпуске изменение поведения. Он просто не вызывает мою функцию. Друг сказал мне, что LLVM оптимизируется до неопределенного значения (LLVM знает, что хвостовая рекурсия бесконечна?!?).

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

Я согласен с Boiethios, языком с функциональными возможностями, который не реализует хвостовой вызов.

Я пишу на C и C++, они не гарантируют хвостовой вызов, но на самом деле они очень хорошо с этим справляются. Это не помешает мне изучить ржавчину, теперь я знаю, что ржавчина не справляется с этим, я буду кодировать без нее, так что это не большая проблема.

Но я думаю, что это очень хорошая особенность для современного языка.


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую, просмотрите его на GitHub (https://github.com/rust-lang/rust/issues/217#issuecomment-237409642) или отключите ветку (https://github.com/notifications/unsubscribe). -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_).

Просто вопрос: гипотетически возможно выполнить анализ графа вызовов и преобразовать хвостовые вызовы в циклы, по крайней мере, в пределах одного ящика, но это требует больших вычислительных ресурсов во время компиляции и довольно сложно кодировать. Было ли обсуждение этой возможности? Это все еще будет вариантом сейчас, независимо от выбора соглашения о вызовах.

Подход lbstanza заключается в том, чтобы требовать аннотации функций, чтобы сделать их подходящими для TCO (defn+ вместо defn). В ржавчине это в значительной степени уменьшит дополнительные накладные расходы на компиляцию по предложению timthelion, просто до тех пор, пока вы не используете хвостовые вызовы буквально везде, лол.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги